diff --git a/cmd/update_configuration/update_configuration.go b/cmd/update_configuration/update_configuration.go deleted file mode 100644 index bc394445..00000000 --- a/cmd/update_configuration/update_configuration.go +++ /dev/null @@ -1,182 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "reflect" - - "github.com/warewulf/warewulf/cmd/update_configuration/vers42" - "github.com/warewulf/warewulf/cmd/update_configuration/vers43" - "github.com/warewulf/warewulf/internal/pkg/util" - "gopkg.in/yaml.v3" -) - -var nowrite bool -var quiet bool -var confFile string - -const actvers int = 43 - -type nodeVersionOnly struct { - WWInternal int `yaml:"WW_INTERNAL"` -} - -func saveConf(conf interface{}) { - out, err := yaml.Marshal(conf) - if err != nil { - myprintf("Error in marshal of conf: %s\n", err) - os.Exit(1) - } - if nowrite { - fmt.Print(string(out)) - } else { - err = util.CopyFile(confFile, confFile+".bak") - if err != nil { - myprintf("Could write file: %s\n", err) - os.Exit(1) - } - info, err := os.Stat(confFile) - if err != nil { - myprintf("Could not get file mode: %s\n", err) - os.Exit(1) - } - myprintf("writing configuration file %s as type %s\n", confFile, reflect.TypeOf(conf)) - err = os.WriteFile(confFile, out, info.Mode()) - if err != nil { - myprintf("Could not write file: %s\n", err) - os.Exit(1) - } - } -} - -func printB(x bool) string { - if x { - return "true" - } - return "false" -} - -func update42to43(conf42 vers42.NodeConf) vers43.NodeConf { - ret := vers43.NodeConf{ - Comment: conf42.Comment, - ClusterName: conf42.ClusterName, - ContainerName: conf42.ContainerName, - Init: conf42.Init, - Root: conf42.Root, - Discoverable: printB(conf42.Discoverable), - Profiles: conf42.Profiles, - Ipxe: conf42.Ipxe} - if conf42.RuntimeOverlay != "" { - ret.RuntimeOverlay = []string{conf42.RuntimeOverlay} - } - if conf42.SystemOverlay != "" { - ret.SystemOverlay = []string{conf42.SystemOverlay} - } - if conf42.KernelArgs != "" || conf42.KernelVersion != "" { - ret.Kernel = new(vers43.KernelConf) - ret.Kernel.Override = conf42.KernelVersion - ret.Kernel.Args = conf42.KernelArgs - - } - if conf42.IpmiUserName != "" || conf42.IpmiPassword != "" || conf42.IpmiIpaddr != "" || - conf42.IpmiNetmask != "" || conf42.IpmiPort != "" || conf42.IpmiGateway != "" || - conf42.IpmiInterface != "" { - ret.Ipmi = new(vers43.IpmiConf) - ret.Ipmi.UserName = conf42.IpmiUserName - ret.Ipmi.Password = conf42.IpmiPassword - ret.Ipmi.Ipaddr = conf42.IpmiIpaddr - ret.Ipmi.Netmask = conf42.IpmiNetmask - ret.Ipmi.Port = conf42.IpmiPort - ret.Ipmi.Gateway = conf42.IpmiGateway - ret.Ipmi.Interface = conf42.IpmiInterface - } - if len(conf42.Keys) != 0 { - ret.Keys = map[string]string{} - for k, v := range conf42.Keys { - ret.Keys[k] = v - } - } - ret.NetDevs = make(map[string]*vers43.NetDevs) - for devn, netdev := range conf42.NetDevs { - var device vers43.NetDevs = vers43.NetDevs{ - Type: netdev.Type, - Device: devn, - Primary: printB(netdev.Default), - Hwaddr: netdev.Hwaddr, - Ipaddr: netdev.Ipaddr, - IpCIDR: netdev.IpCIDR, - Prefix: netdev.Prefix, - Netmask: netdev.Netmask, - Gateway: netdev.Gateway} - ret.NetDevs[devn] = &device - } - return ret -} - -func myprintf(format string, a ...interface{}) { - if !quiet { - fmt.Printf(format, a...) - } -} - -func main() { - var endVers int - var startVers int - flag.StringVar(&confFile, "f", "", "Config file for update") - flag.IntVar(&endVers, "e", 0, "Final version of configuration file") - flag.IntVar(&startVers, "s", 0, "Start version of configuration file, 0 is for autodetection") - flag.BoolVar(&nowrite, "n", false, "Do not write, just print new conf to terminal") - flag.BoolVar(&quiet, "q", false, "Do not print what the program is doing") - flag.Parse() - if confFile == "" { - myprintf("No config file given\n!") - os.Exit(1) - } - myprintf("Opening node configuration file: %s\n", confFile) - data, err := os.ReadFile(confFile) - if err != nil { - myprintf("Could open file %v\n", err) - os.Exit(1) - } - var getConf nodeVersionOnly - myprintf("Unmarshaling the node configuration\n") - err = yaml.Unmarshal(data, &getConf) - if err != nil { - myprintf("Could not unmarshall: %v\n", err) - } - myprintf("Got version %v in %s\n", getConf.WWInternal, confFile) - if getConf.WWInternal == actvers { - myprintf("On actual version, bailing out\n") - os.Exit(0) - } - if startVers == 0 && getConf.WWInternal == 0 { - startVers = 42 - } - var conf42 vers42.NodeYaml - conf42.NodeProfiles = make(map[string]*vers42.NodeConf) - conf42.Nodes = make(map[string]*vers42.NodeConf) - - var conf43 vers43.NodeYaml - conf43.NodeProfiles = make(map[string]*vers43.NodeConf) - conf43.Nodes = make(map[string]*vers43.NodeConf) - conf43.WWInternal = 43 - - if startVers == 42 { - myprintf("Unmarshaling the node configuration vers 42\n") - err = yaml.Unmarshal(data, &conf42) - if err != nil { - myprintf("Could not unmarshall version 42: %v\n", err) - } - for pname, profile := range conf42.NodeProfiles { - p43 := update42to43(*profile) - conf43.NodeProfiles[pname] = &p43 - } - for nname, node := range conf42.Nodes { - n43 := update42to43(*node) - conf43.Nodes[nname] = &n43 - } - saveConf(conf43) - - } -} diff --git a/cmd/update_configuration/vers42/datastructure.go b/cmd/update_configuration/vers42/datastructure.go deleted file mode 100644 index 188419d8..00000000 --- a/cmd/update_configuration/vers42/datastructure.go +++ /dev/null @@ -1,46 +0,0 @@ -package vers42 - -/****** - * YAML data representations - ******/ - -// type nodeYaml struct { -type NodeYaml struct { // <-Needs to be exported - NodeProfiles map[string]*NodeConf - Nodes map[string]*NodeConf -} - -type NodeConf struct { - Comment string `yaml:"comment,omitempty"` - ClusterName string `yaml:"cluster name,omitempty"` - ContainerName string `yaml:"container name,omitempty"` - Ipxe string `yaml:"ipxe template,omitempty"` - KernelVersion string `yaml:"kernel version,omitempty"` - KernelArgs string `yaml:"kernel args,omitempty"` - IpmiUserName string `yaml:"ipmi username,omitempty"` - IpmiPassword string `yaml:"ipmi password,omitempty"` - IpmiIpaddr string `yaml:"ipmi ipaddr,omitempty"` - IpmiNetmask string `yaml:"ipmi netmask,omitempty"` - IpmiPort string `yaml:"ipmi port,omitempty"` - IpmiGateway string `yaml:"ipmi gateway,omitempty"` - IpmiInterface string `yaml:"ipmi interface,omitempty"` - RuntimeOverlay string `yaml:"runtime overlay,omitempty"` - SystemOverlay string `yaml:"system overlay,omitempty"` - Init string `yaml:"init,omitempty"` - Root string `yaml:"root,omitempty"` - Discoverable bool `yaml:"discoverable,omitempty"` - Profiles []string `yaml:"profiles,omitempty"` - NetDevs map[string]*NetDevs `yaml:"network devices,omitempty"` - Keys map[string]string `yaml:"keys,omitempty"` -} - -type NetDevs struct { - Type string `yaml:"type,omitempty"` - Default bool `yaml:"default"` - Hwaddr string - Ipaddr string - IpCIDR string - Prefix string - Netmask string - Gateway string `yaml:"gateway,omitempty"` -} diff --git a/cmd/update_configuration/vers42/nodes.conf b/cmd/update_configuration/vers42/nodes.conf deleted file mode 100644 index 792e48a2..00000000 --- a/cmd/update_configuration/vers42/nodes.conf +++ /dev/null @@ -1,22 +0,0 @@ -nodeprofiles: - default: - comment: This profile is automatically included for each node - runtime overlay: "generic" - discoverable: false - leap: - comment: openSUSE leap - kernel version: "5.14.21" - ipmi netmask: "255.255.255.0" - keys: - foo: baar - network devices: - lan1: - gateway: 1.1.1.1 -nodes: - node01: - system overlay: "nodeoverlay" - discoverable: true - network devices: - eth0: - ipaddr: 1.2.3.4 - default: true diff --git a/cmd/update_configuration/vers43/datastructure.go b/cmd/update_configuration/vers43/datastructure.go deleted file mode 100644 index 24e8f185..00000000 --- a/cmd/update_configuration/vers43/datastructure.go +++ /dev/null @@ -1,65 +0,0 @@ -package vers43 - -/****** - * YAML data representations - ******/ - -// type nodeYaml struct { -type NodeYaml struct { // <- needs to be exported - WWInternal int `yaml:"WW_INTERNAL"` - NodeProfiles map[string]*NodeConf - Nodes map[string]*NodeConf -} - -/* -NodeConf is the datastructure which is stored on disk. -*/ -type NodeConf struct { - Comment string `yaml:"comment,omitempty"` - ClusterName string `yaml:"cluster name,omitempty"` - ContainerName string `yaml:"container name,omitempty"` - Ipxe string `yaml:"ipxe template,omitempty"` - RuntimeOverlay []string `yaml:"runtime overlay,omitempty"` - SystemOverlay []string `yaml:"system overlay,omitempty"` - Kernel *KernelConf `yaml:"kernel,omitempty"` - Ipmi *IpmiConf `yaml:"ipmi,omitempty"` - Init string `yaml:"init,omitempty"` - Root string `yaml:"root,omitempty"` - AssetKey string `yaml:"asset key,omitempty"` - Discoverable string `yaml:"discoverable,omitempty"` - Profiles []string `yaml:"profiles,omitempty"` - NetDevs map[string]*NetDevs `yaml:"network devices,omitempty"` - Tags map[string]string `yaml:"tags,omitempty"` - Keys map[string]string `yaml:"keys,omitempty"` // Reverse compatibility -} - -type IpmiConf struct { - UserName string `yaml:"username,omitempty"` - Password string `yaml:"password,omitempty"` - Ipaddr string `yaml:"ipaddr,omitempty"` - Netmask string `yaml:"netmask,omitempty"` - Port string `yaml:"port,omitempty"` - Gateway string `yaml:"gateway,omitempty"` - Interface string `yaml:"interface,omitempty"` - Write bool `yaml:"write,omitempty"` -} -type KernelConf struct { - Version string `yaml:"version,omitempty"` - Override string `yaml:"override,omitempty"` - Args string `yaml:"args,omitempty"` -} - -type NetDevs struct { - Type string `yaml:"type,omitempty"` - OnBoot string `yaml:"onboot,omitempty"` - Device string `yaml:"device,omitempty"` - Hwaddr string `yaml:"hwaddr,omitempty"` - Ipaddr string `yaml:"ipaddr,omitempty"` - IpCIDR string `yaml:"ipcidr,omitempty"` - Ipaddr6 string `yaml:"ip6addr,omitempty"` - Prefix string `yaml:"prefix,omitempty"` - Netmask string `yaml:"netmask,omitempty"` - Gateway string `yaml:"gateway,omitempty"` - Primary string `yaml:"primary,omitempty"` - Tags map[string]string `yaml:"tags,omitempty"` -} diff --git a/cmd/update_configuration/vers43/nodes.conf b/cmd/update_configuration/vers43/nodes.conf deleted file mode 100644 index 22772050..00000000 --- a/cmd/update_configuration/vers43/nodes.conf +++ /dev/null @@ -1,32 +0,0 @@ -WW_INTERNAL: 45 -nodeprofiles: - default: - comment: This profile is automatically included for each node - runtime overlay: - - generic - discoverable: "false" - leap: - comment: openSUSE leap - kernel: - override: 5.14.21 - ipmi: - netmask: 255.255.255.0 - discoverable: "false" - network devices: - lan1: - device: lan1 - gateway: 1.1.1.1 - default: "false" - keys: - foo: baar -nodes: - node01: - system overlay: - - nodeoverlay - discoverable: "true" - network devices: - eth0: - device: eth0 - ipaddr: 1.2.3.4 - default: "true" - diff --git a/internal/pkg/upgrade/conf.go b/internal/pkg/upgrade/conf.go index 24803844..ddba7018 100644 --- a/internal/pkg/upgrade/conf.go +++ b/internal/pkg/upgrade/conf.go @@ -1,7 +1,7 @@ package upgrade type WarewulfYaml struct { - WWInternal int `yaml:"WW_INTERNAL"` + WWInternal string `yaml:"WW_INTERNAL"` Comment string `yaml:"comment,omitempty"` Ipaddr string `yaml:"ipaddr"` Ipaddr6 string `yaml:"ipaddr6,omitempty"` diff --git a/internal/pkg/upgrade/node.go b/internal/pkg/upgrade/node.go index 460a75f6..e6d57cf7 100644 --- a/internal/pkg/upgrade/node.go +++ b/internal/pkg/upgrade/node.go @@ -1,31 +1,194 @@ package upgrade import ( + "net" + "strconv" + "strings" + "gopkg.in/yaml.v3" + + "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/wwlog" ) -func Parse(data []byte) (nodeYaml NodeYaml, err error) { - if err = yaml.Unmarshal(data, &nodeYaml); err != nil { - return nodeYaml, err - } - if nodeYaml.Nodes == nil { - nodeYaml.Nodes = map[string]*Node{} - } - if nodeYaml.NodeProfiles == nil { - nodeYaml.NodeProfiles = map[string]*Profile{} - } - return nodeYaml, nil +func logIgnore(name string, value interface{}, reason string) { + wwlog.Warn("ignore: %s: %v (%s)", name, value, reason) } -type NodeYaml struct { +func Parse(data []byte) (nodesYaml *NodesYaml, err error) { + nodesYaml = new(NodesYaml) + if err = yaml.Unmarshal(data, nodesYaml); err != nil { + return nodesYaml, err + } + return nodesYaml, nil +} + +type NodesYaml struct { + WWInternal string `yaml:"WW_INTERNAL"` NodeProfiles map[string]*Profile Nodes map[string]*Node } +func (this *NodesYaml) Upgrade() (upgraded *node.NodesYaml) { + upgraded = new(node.NodesYaml) + upgraded.NodeProfiles = make(map[string]*node.Profile) + upgraded.Nodes = make(map[string]*node.Node) + if this.WWInternal != "" { + logIgnore("WW_INTERNAL", this.WWInternal, "obsolete") + } + if this.NodeProfiles != nil { + for name, profile := range this.NodeProfiles { + upgraded.NodeProfiles[name] = profile.Upgrade() + } + } + if this.Nodes != nil { + for name, node := range this.Nodes { + upgraded.Nodes[name] = node.Upgrade() + } + } + return upgraded +} + type Node struct { Profile `yaml:"-,inline"` } +func (this *Node) Upgrade() (upgraded *node.Node) { + upgraded = new(node.Node) + upgraded.Tags = make(map[string]string) + upgraded.Disks = make(map[string]*node.Disk) + upgraded.FileSystems = make(map[string]*node.FileSystem) + upgraded.Ipmi = new(node.IpmiConf) + upgraded.Kernel = new(node.KernelConf) + upgraded.NetDevs = make(map[string]*node.NetDev) + upgraded.AssetKey = this.AssetKey + upgraded.ClusterName = this.ClusterName + upgraded.Comment = this.Comment + upgraded.ContainerName = this.ContainerName + if this.Disabled != "" { + logIgnore("Disabled", this.Disabled, "obsolete") + } + if this.Discoverable != "" { + upgraded.Discoverable.Set(this.Discoverable) + } + if this.Disks != nil { + for name, disk := range this.Disks { + upgraded.Disks[name] = disk.Upgrade() + } + } + if this.FileSystems != nil { + for name, fileSystem := range this.FileSystems { + upgraded.FileSystems[name] = fileSystem.Upgrade() + } + } + upgraded.Init = this.Init + if this.Ipmi != nil { + upgraded.Ipmi = this.Ipmi.Upgrade() + } else { + upgraded.Ipmi = new(node.IpmiConf) + } + if upgraded.Ipmi.EscapeChar == "" { + upgraded.Ipmi.EscapeChar = this.IpmiEscapeChar + } + if upgraded.Ipmi.Gateway.Equal(net.IP{}) { + upgraded.Ipmi.Gateway = net.ParseIP(this.IpmiGateway) + } + if upgraded.Ipmi.Interface == "" { + upgraded.Ipmi.Interface = this.IpmiInterface + } + if upgraded.Ipmi.Ipaddr.Equal(net.IP{}) { + upgraded.Ipmi.Ipaddr = net.ParseIP(this.IpmiIpaddr) + } + if upgraded.Ipmi.Netmask.Equal(net.IP{}) { + upgraded.Ipmi.Netmask = net.ParseIP(this.IpmiNetmask) + } + if upgraded.Ipmi.Password == "" { + upgraded.Ipmi.Password = this.IpmiPassword + } + if upgraded.Ipmi.Port == "" { + upgraded.Ipmi.Port = this.IpmiPort + } + if upgraded.Ipmi.UserName == "" { + upgraded.Ipmi.UserName = this.IpmiUserName + } + if upgraded.Ipmi.Write == "" { + upgraded.Ipmi.Write.Set(this.IpmiWrite) + } + upgraded.Ipxe = this.Ipxe + if this.Kernel != nil { + upgraded.Kernel = this.Kernel.Upgrade() + } else { + upgraded.Kernel = new(node.KernelConf) + } + if upgraded.Kernel.Args == "" { + upgraded.Kernel.Args = this.KernelArgs + } + if upgraded.Kernel.Override == "" { + upgraded.Kernel.Override = this.KernelOverride + } + if upgraded.Kernel.Version == "" { + upgraded.Kernel.Version = this.KernelVersion + } + if this.Keys != nil { + for key, value := range this.Keys { + upgraded.Tags[key] = value + } + } + if this.NetDevs != nil { + for name, netDev := range this.NetDevs { + upgraded.NetDevs[name] = netDev.Upgrade() + } + } + if this.PrimaryNetDev != "" { + upgraded.PrimaryNetDev = this.PrimaryNetDev + } else { + for name, netDev := range this.NetDevs { + if b, _ := strconv.ParseBool(netDev.Primary); b { + upgraded.PrimaryNetDev = name + break + } else if b, _ := strconv.ParseBool(netDev.Default); b { + upgraded.PrimaryNetDev = name + break + } + } + } + upgraded.Profiles = append(upgraded.Profiles, this.Profiles...) + upgraded.Root = this.Root + if this.RuntimeOverlay != nil { + switch overlay := this.RuntimeOverlay.(type) { + case string: + upgraded.RuntimeOverlay = append(upgraded.RuntimeOverlay, strings.Split(overlay, ",")...) + case []interface{}: + for _, each := range overlay { + upgraded.RuntimeOverlay = append(upgraded.RuntimeOverlay, each.(string)) + } + default: + wwlog.Error("unparsable RuntimeOverlay: %v", overlay) + } + } + if this.SystemOverlay != nil { + switch overlay := this.SystemOverlay.(type) { + case string: + upgraded.SystemOverlay = append(upgraded.SystemOverlay, strings.Split(overlay, ",")...) + case []interface{}: + for _, each := range overlay { + upgraded.SystemOverlay = append(upgraded.SystemOverlay, each.(string)) + } + default: + wwlog.Error("unparsable SystemOverlay: %v", overlay) + } + } + if this.Tags != nil { + for key, value := range this.Tags { + upgraded.Tags[key] = value + } + } + for _, tag := range this.TagsDel { + delete(upgraded.Tags, tag) + } + return +} + type Profile struct { AssetKey string `yaml:"asset key,omitempty"` ClusterName string `yaml:"cluster name,omitempty"` @@ -56,12 +219,152 @@ type Profile struct { PrimaryNetDev string `yaml:"primary network,omitempty"` Profiles []string `yaml:"profiles,omitempty"` Root string `yaml:"root,omitempty"` - RuntimeOverlay []string `yaml:"runtime overlay,omitempty"` - SystemOverlay []string `yaml:"system overlay,omitempty"` + RuntimeOverlay interface{} `yaml:"runtime overlay,omitempty"` + SystemOverlay interface{} `yaml:"system overlay,omitempty"` Tags map[string]string `yaml:"tags,omitempty"` TagsDel []string `yaml:"tagsdel,omitempty"` } +func (this *Profile) Upgrade() (upgraded *node.Profile) { + upgraded = new(node.Profile) + upgraded.Tags = make(map[string]string) + upgraded.Disks = make(map[string]*node.Disk) + upgraded.FileSystems = make(map[string]*node.FileSystem) + upgraded.Kernel = new(node.KernelConf) + upgraded.NetDevs = make(map[string]*node.NetDev) + if this.AssetKey != "" { + logIgnore("AssetKey", this.AssetKey, "invalid for profiles") + } + upgraded.ClusterName = this.ClusterName + upgraded.Comment = this.Comment + upgraded.ContainerName = this.ContainerName + if this.Disabled != "" { + logIgnore("Disabled", this.Disabled, "obsolete") + } + if this.Discoverable != "" { + logIgnore("Discoverable", this.Discoverable, "invalid for profiles") + } + if this.Disks != nil { + for name, disk := range this.Disks { + upgraded.Disks[name] = disk.Upgrade() + } + } + if this.FileSystems != nil { + for name, fileSystem := range this.FileSystems { + upgraded.FileSystems[name] = fileSystem.Upgrade() + } + } + upgraded.Init = this.Init + upgraded.Ipmi = new(node.IpmiConf) + if this.Ipmi != nil { + upgraded.Ipmi = this.Ipmi.Upgrade() + } else { + upgraded.Ipmi = new(node.IpmiConf) + } + if upgraded.Ipmi.EscapeChar == "" { + upgraded.Ipmi.EscapeChar = this.IpmiEscapeChar + } + if upgraded.Ipmi.Gateway.Equal(net.IP{}) { + upgraded.Ipmi.Gateway = net.ParseIP(this.IpmiGateway) + } + if upgraded.Ipmi.Interface == "" { + upgraded.Ipmi.Interface = this.IpmiInterface + } + if upgraded.Ipmi.Ipaddr.Equal(net.IP{}) { + upgraded.Ipmi.Ipaddr = net.ParseIP(this.IpmiIpaddr) + } + if upgraded.Ipmi.Netmask.Equal(net.IP{}) { + upgraded.Ipmi.Netmask = net.ParseIP(this.IpmiNetmask) + } + if upgraded.Ipmi.Password == "" { + upgraded.Ipmi.Password = this.IpmiPassword + } + if upgraded.Ipmi.Port == "" { + upgraded.Ipmi.Port = this.IpmiPort + } + if upgraded.Ipmi.UserName == "" { + upgraded.Ipmi.UserName = this.IpmiUserName + } + if upgraded.Ipmi.Write == "" { + upgraded.Ipmi.Write.Set(this.IpmiWrite) + } + upgraded.Ipxe = this.Ipxe + if this.Kernel != nil { + upgraded.Kernel = this.Kernel.Upgrade() + } else { + upgraded.Kernel = new(node.KernelConf) + } + if upgraded.Kernel.Args == "" { + upgraded.Kernel.Args = this.KernelArgs + } + if upgraded.Kernel.Override == "" { + upgraded.Kernel.Override = this.KernelOverride + } + if upgraded.Kernel.Version == "" { + upgraded.Kernel.Version = this.KernelVersion + } + if this.Keys != nil { + for key, value := range this.Keys { + upgraded.Tags[key] = value + } + } + if this.NetDevs != nil { + for name, netDev := range this.NetDevs { + upgraded.NetDevs[name] = netDev.Upgrade() + } + } + if this.PrimaryNetDev != "" { + upgraded.PrimaryNetDev = this.PrimaryNetDev + } else { + for name, netDev := range this.NetDevs { + if b, _ := strconv.ParseBool(netDev.Primary); b { + upgraded.PrimaryNetDev = name + break + } else if b, _ := strconv.ParseBool(netDev.Default); b { + upgraded.PrimaryNetDev = name + break + } + } + } + if this.Profiles != nil { + logIgnore("Profiles", this.Profiles, "invalid for profiles") + } + upgraded.Root = this.Root + if this.RuntimeOverlay != nil { + switch overlay := this.RuntimeOverlay.(type) { + case string: + upgraded.RuntimeOverlay = append(upgraded.RuntimeOverlay, strings.Split(overlay, ",")...) + case []interface{}: + for _, each := range overlay { + upgraded.RuntimeOverlay = append(upgraded.RuntimeOverlay, each.(string)) + } + default: + wwlog.Error("unparsable RuntimeOverlay: %v", overlay) + } + } + if this.SystemOverlay != nil { + switch overlay := this.SystemOverlay.(type) { + case string: + upgraded.SystemOverlay = append(upgraded.SystemOverlay, strings.Split(overlay, ",")...) + case []interface{}: + for _, each := range overlay { + upgraded.SystemOverlay = append(upgraded.SystemOverlay, each.(string)) + } + default: + wwlog.Error("unparsable SystemOverlay: %v", overlay) + } + } + if this.Tags != nil { + for key, value := range this.Tags { + upgraded.Tags[key] = value + } + } + for _, tag := range this.TagsDel { + delete(upgraded.Tags, tag) + } + return +} + type IpmiConf struct { EscapeChar string `yaml:"escapechar,omitempty"` Gateway string `yaml:"gateway,omitempty"` @@ -76,12 +379,43 @@ type IpmiConf struct { Write string `yaml:"write,omitempty"` } +func (this *IpmiConf) Upgrade() (upgraded *node.IpmiConf) { + upgraded = new(node.IpmiConf) + upgraded.Tags = make(map[string]string) + upgraded.EscapeChar = this.EscapeChar + upgraded.Gateway = net.ParseIP(this.Gateway) + upgraded.Interface = this.Interface + upgraded.Ipaddr = net.ParseIP(this.Ipaddr) + upgraded.Netmask = net.ParseIP(this.Netmask) + upgraded.Password = this.Password + upgraded.Port = this.Port + if this.Tags != nil { + for key, value := range this.Tags { + upgraded.Tags[key] = value + } + } + for _, tag := range this.TagsDel { + delete(upgraded.Tags, tag) + } + upgraded.UserName = this.UserName + upgraded.Write.Set(this.Write) + return +} + type KernelConf struct { Args string `yaml:"args,omitempty"` Override string `yaml:"override,omitempty"` Version string `yaml:"version,omitempty"` } +func (this *KernelConf) Upgrade() (upgraded *node.KernelConf) { + upgraded = new(node.KernelConf) + upgraded.Args = this.Args + upgraded.Override = this.Override + upgraded.Version = this.Version + return +} + type NetDev struct { Default string `yaml:"default"` Device string `yaml:"device,omitempty"` @@ -100,11 +434,60 @@ type NetDev struct { Type string `yaml:"type,omitempty"` } +func (this *NetDev) Upgrade() (upgraded *node.NetDev) { + upgraded = new(node.NetDev) + upgraded.Tags = make(map[string]string) + upgraded.Device = this.Device + upgraded.Gateway = net.ParseIP(this.Gateway) + upgraded.Hwaddr = this.Hwaddr + upgraded.Ipaddr = net.ParseIP(this.Ipaddr) + upgraded.Ipaddr6 = net.ParseIP(this.Ipaddr6) + upgraded.MTU = this.MTU + upgraded.Netmask = net.ParseIP(this.Netmask) + if this.IpCIDR != "" { + cidrIP, cidrIPNet, err := net.ParseCIDR(this.IpCIDR) + if err != nil { + wwlog.Error("%v is not a valid CIDR address: %w", this.IpCIDR, err) + } else { + if upgraded.Ipaddr == nil { + upgraded.Ipaddr = cidrIP + } + if upgraded.Netmask == nil { + upgraded.Netmask = net.IP(cidrIPNet.Mask) + } + } + } + upgraded.OnBoot.Set(this.OnBoot) + upgraded.Prefix = net.ParseIP(this.Prefix) + if this.Tags != nil { + for key, value := range this.Tags { + upgraded.Tags[key] = value + } + } + for _, tag := range this.TagsDel { + delete(upgraded.Tags, tag) + } + upgraded.Type = this.Type + return +} + type Disk struct { Partitions map[string]*Partition `yaml:"partitions,omitempty"` WipeTable string `yaml:"wipe_table,omitempty"` } +func (this *Disk) Upgrade() (upgraded *node.Disk) { + upgraded = new(node.Disk) + upgraded.Partitions = make(map[string]*node.Partition) + if this.Partitions != nil { + for name, partition := range this.Partitions { + upgraded.Partitions[name] = partition.Upgrade() + } + } + upgraded.WipeTable, _ = strconv.ParseBool(this.WipeTable) + return +} + type Partition struct { Guid string `yaml:"guid,omitempty"` Number string `yaml:"number,omitempty"` @@ -116,13 +499,49 @@ type Partition struct { WipePartitionEntry string `yaml:"wipe_partition_entry,omitempty"` } -type FileSystem struct { - Format string `yaml:"format,omitempty"` - Label string `yaml:"label,omitempty"` - MountOptions string `yaml:"mount_options,omitempty"` - //MountOptions []string `yaml:"mount_options,omitempty"` - Options []string `yaml:"options,omitempty"` - Path string `yaml:"path,omitempty"` - Uuid string `yaml:"uuid,omitempty"` - WipeFileSystem string `yaml:"wipe_filesystem,omitempty"` +func (this *Partition) Upgrade() (upgraded *node.Partition) { + upgraded = new(node.Partition) + upgraded.Guid = this.Guid + upgraded.Number = this.Number + upgraded.Resize, _ = strconv.ParseBool(this.Resize) + upgraded.ShouldExist, _ = strconv.ParseBool(this.ShouldExist) + upgraded.SizeMiB = this.SizeMiB + upgraded.StartMiB = this.StartMiB + upgraded.TypeGuid = this.TypeGuid + upgraded.WipePartitionEntry, _ = strconv.ParseBool(this.WipePartitionEntry) + return +} + +type FileSystem struct { + Format string `yaml:"format,omitempty"` + Label string `yaml:"label,omitempty"` + MountOptions interface{} `yaml:"mount_options,omitempty"` + Options []string `yaml:"options,omitempty"` + Path string `yaml:"path,omitempty"` + Uuid string `yaml:"uuid,omitempty"` + WipeFileSystem string `yaml:"wipe_filesystem,omitempty"` +} + +func (this *FileSystem) Upgrade() (upgraded *node.FileSystem) { + upgraded = new(node.FileSystem) + upgraded.Options = make([]string, 0) + upgraded.Format = this.Format + upgraded.Label = this.Label + switch mountOptions := this.MountOptions.(type) { + case string: + upgraded.MountOptions = mountOptions + case []interface{}: + mountOptionsStrings := make([]string, 0) + for _, option := range mountOptions { + mountOptionsStrings = append(mountOptionsStrings, option.(string)) + } + upgraded.MountOptions = strings.Join(mountOptionsStrings, " ") + default: + wwlog.Error("unparsable MountOptions: %v", mountOptions) + } + upgraded.Options = append(upgraded.Options, this.Options...) + upgraded.Path = this.Path + upgraded.Uuid = this.Uuid + upgraded.WipeFileSystem, _ = strconv.ParseBool(this.WipeFileSystem) + return } diff --git a/internal/pkg/upgrade/node_test.go b/internal/pkg/upgrade/node_test.go new file mode 100644 index 00000000..06ae79ee --- /dev/null +++ b/internal/pkg/upgrade/node_test.go @@ -0,0 +1,502 @@ +package upgrade + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +var nodesYamlUpgradeTests = []struct { + name string + legacyYaml string + upgradedYaml string +}{ + { + name: "captured vers42 example", + legacyYaml: ` +nodeprofiles: + default: + comment: This profile is automatically included for each node + runtime overlay: "generic" + discoverable: false + leap: + comment: openSUSE leap + kernel version: 5.14.21 + ipmi netmask: "255.255.255.0" + keys: + foo: baar + network devices: + lan1: + gateway: 1.1.1.1 +nodes: + node01: + system overlay: "nodeoverlay" + discoverable: true + network devices: + eth0: + ipaddr: 1.2.3.4 + default: true +`, + upgradedYaml: ` +nodeprofiles: + default: + comment: This profile is automatically included for each node + runtime overlay: + - generic + leap: + comment: openSUSE leap + kernel: + version: 5.14.21 + ipmi: + netmask: 255.255.255.0 + network devices: + lan1: + gateway: 1.1.1.1 + tags: + foo: baar +nodes: + node01: + discoverable: "true" + system overlay: + - nodeoverlay + network devices: + eth0: + ipaddr: 1.2.3.4 + primary network: eth0 +`, + }, + { + name: "captured vers43 example", + legacyYaml: ` +WW_INTERNAL: 45 +nodeprofiles: + default: + comment: This profile is automatically included for each node + runtime overlay: + - generic + discoverable: "false" + leap: + comment: openSUSE leap + kernel: + override: 5.14.21 + ipmi: + netmask: 255.255.255.0 + discoverable: "false" + network devices: + lan1: + device: lan1 + gateway: 1.1.1.1 + default: "false" + keys: + foo: baar +nodes: + node01: + system overlay: + - nodeoverlay + discoverable: "true" + network devices: + eth0: + device: eth0 + ipaddr: 1.2.3.4 + default: "true" +`, + upgradedYaml: ` +nodeprofiles: + default: + comment: This profile is automatically included for each node + runtime overlay: + - generic + leap: + comment: openSUSE leap + kernel: + override: 5.14.21 + ipmi: + netmask: 255.255.255.0 + network devices: + lan1: + device: lan1 + gateway: 1.1.1.1 + tags: + foo: baar +nodes: + node01: + discoverable: "true" + system overlay: + - nodeoverlay + network devices: + eth0: + device: eth0 + ipaddr: 1.2.3.4 + primary network: eth0 +`, + }, + { + name: "remove WW_INTERNAL", + legacyYaml: `WW_INTERNAL: 45`, + upgradedYaml: ` +nodeprofiles: {} +nodes: {} +`, + }, + { + name: "disabled is obsolete", + legacyYaml: ` +nodes: + n1: + disabled: true +nodeprofiles: + default: + disabled: true +`, + upgradedYaml: ` +nodeprofiles: + default: {} +nodes: + n1: {} +`, + }, + { + name: "inline IPMI settings", + legacyYaml: ` +nodes: + n1: + ipmi escapechar: "~" + ipmi gateway: 192.168.0.1 + ipmi interface: lanplus + ipmi ipaddr: 192.168.0.100 + ipmi netmask: 255.255.255.0 + ipmi password: password + ipmi port: 623 + ipmi username: admin + ipmi write: true +nodeprofiles: + default: + ipmi escapechar: "~" + ipmi gateway: 192.168.0.1 + ipmi interface: lanplus + ipmi ipaddr: 192.168.0.100 + ipmi netmask: 255.255.255.0 + ipmi password: password + ipmi port: 623 + ipmi username: admin + ipmi write: true +`, + upgradedYaml: ` +nodeprofiles: + default: + ipmi: + username: admin + password: password + ipaddr: 192.168.0.100 + gateway: 192.168.0.1 + netmask: 255.255.255.0 + port: "623" + interface: lanplus + escapechar: "~" + write: "true" +nodes: + n1: + ipmi: + username: admin + password: password + ipaddr: 192.168.0.100 + gateway: 192.168.0.1 + netmask: 255.255.255.0 + port: "623" + interface: lanplus + escapechar: "~" + write: "true" +`, + }, + { + name: "inline Kernel settings", + legacyYaml: ` +nodeprofiles: + default: + kernel args: quiet + kernel override: rockylinux-9 + kernel version: 2.6 +nodes: + n1: + kernel args: quiet + kernel override: rockylinux-9 + kernel version: 2.6 +`, + upgradedYaml: ` +nodeprofiles: + default: + kernel: + version: "2.6" + override: rockylinux-9 + args: quiet +nodes: + n1: + kernel: + version: "2.6" + override: rockylinux-9 + args: quiet +`, + }, + { + name: "keys and tags", + legacyYaml: ` +nodeprofiles: + default: + keys: + key1: val1 + key2: val2 + tags: + key2: valB + key3: valC + key4: valD + tagsdel: + - key4 + network devices: + default: + tags: + key2: valB + key3: valC + key4: valD + tagsdel: + - key4 + ipmi: + tags: + key2: valB + key3: valC + key4: valD + tagsdel: + - key4 +nodes: + n1: + keys: + key1: val1 + key2: val2 + tags: + key2: valB + key3: valC + key4: valD + tagsdel: + - key4 + network devices: + default: + tags: + key2: valB + key3: valC + key4: valD + tagsdel: + - key4 + ipmi: + tags: + key2: valB + key3: valC + key4: valD + tagsdel: + - key4 +`, + upgradedYaml: ` +nodeprofiles: + default: + ipmi: + tags: + key2: valB + key3: valC + network devices: + default: + tags: + key2: valB + key3: valC + tags: + key1: val1 + key2: valB + key3: valC +nodes: + n1: + ipmi: + tags: + key2: valB + key3: valC + network devices: + default: + tags: + key2: valB + key3: valC + tags: + key1: val1 + key2: valB + key3: valC +`, + }, + { + name: "primary network", + legacyYaml: ` +nodes: + n1: + network devices: + eth0: {} + eth1: + default: true + n2: + network devices: + eth0: + primary: true + eth1: {} + n3: + network devices: + eth0: + primary: true + eth1: {} + primary network: eth1 +nodeprofiles: + p1: + network devices: + eth0: {} + eth1: + default: true + p2: + network devices: + eth0: + primary: true + eth1: {} + p3: + network devices: + eth0: + primary: true + eth1: {} + primary network: eth1 +`, + upgradedYaml: ` +nodeprofiles: + p1: + primary network: eth1 + p2: + primary network: eth0 + p3: + primary network: eth1 +nodes: + n1: + primary network: eth1 + n2: + primary network: eth0 + n3: + primary network: eth1 +`, + }, + { + name: "overlays", + legacyYaml: ` +nodes: + n1: + runtime overlay: + - r1 + - r2 + system overlay: + - s1 + - s2 + n2: + runtime overlay: r1,r2 + system overlay: s1,s2 +nodeprofiles: + p1: + runtime overlay: + - r1 + - r2 + system overlay: + - s1 + - s2 + p2: + runtime overlay: r1,r2 + system overlay: s1,s2 +`, + upgradedYaml: ` +nodeprofiles: + p1: + runtime overlay: + - r1 + - r2 + system overlay: + - s1 + - s2 + p2: + runtime overlay: + - r1 + - r2 + system overlay: + - s1 + - s2 +nodes: + n1: + runtime overlay: + - r1 + - r2 + system overlay: + - s1 + - s2 + n2: + runtime overlay: + - r1 + - r2 + system overlay: + - s1 + - s2 +`, + }, + { + name: "disk example", + legacyYaml: ` +nodes: + n1: + disks: + /dev/vda: + wipe_table: true + partitions: + scratch: + number: "1" + should_exist: true + swap: + number: "2" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + /dev/disk/by-partlabel/swap: + format: swap + path: swap +`, + upgradedYaml: ` +nodeprofiles: {} +nodes: + n1: + disks: + /dev/vda: + wipe_table: true + partitions: + scratch: + number: "1" + should_exist: true + swap: + number: "2" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + /dev/disk/by-partlabel/swap: + format: swap + path: swap +`, + }, +} + +func Test_UpgradeNodesYaml(t *testing.T) { + for _, tt := range nodesYamlUpgradeTests { + t.Run(tt.name, func(t *testing.T) { + legacy, err := Parse([]byte(tt.legacyYaml)) + assert.NoError(t, err) + upgraded := legacy.Upgrade() + upgradedYaml, err := upgraded.Dump() + assert.Equal(t, strings.TrimSpace(tt.upgradedYaml), strings.TrimSpace(string(upgradedYaml))) + }) + } +}