diff --git a/internal/app/wwctl/upgrade/cobra.go b/internal/app/wwctl/upgrade/cobra.go index c871c1f2..88e1a611 100644 --- a/internal/app/wwctl/upgrade/cobra.go +++ b/internal/app/wwctl/upgrade/cobra.go @@ -1,60 +1,23 @@ package upgrade import ( - "fmt" - "os" - "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/pkg/node" - libupgrade "github.com/warewulf/warewulf/internal/pkg/upgrade" - "github.com/warewulf/warewulf/internal/pkg/util" + "github.com/warewulf/warewulf/internal/app/wwctl/upgrade/config" + "github.com/warewulf/warewulf/internal/app/wwctl/upgrade/nodes" ) var ( Command = &cobra.Command{ DisableFlagsInUseLine: true, - Use: "upgrade [OPTIONS]", - Short: "Upgrade an existing nodes.conf", - Long: `Upgrades nodes.conf from a previous version of Warewulf 4 to a format + Use: "upgrade [OPTIONS]", + Short: "Upgrade configuration files", + Long: `Upgrade warewulf.conf or nodes.conf from a previous version of Warewulf 4 to a format supported by the current version.`, - RunE: UpgradeNodesConf, } - - addDefaults bool - replaceOverlays bool - inputPath string - outputPath string ) func init() { - Command.Flags().BoolVar(&addDefaults, "add-defaults", false, "Configure a default profile and set default node values") - Command.Flags().BoolVar(&replaceOverlays, "replace-overlays", false, "Replace 'wwinit' and 'generic' overlays with their split replacements") - Command.Flags().StringVarP(&inputPath, "input-path", "i", node.ConfigFile, "Path to a legacy nodes.conf") - Command.Flags().StringVarP(&outputPath, "output-path", "o", node.ConfigFile, "Path to write the upgraded nodes.conf to") -} - -func UpgradeNodesConf(cmd *cobra.Command, args []string) error { - data, err := os.ReadFile(inputPath) - if err != nil { - return err - } - legacy, err := libupgrade.Parse(data) - if err != nil { - return err - } - upgraded := legacy.Upgrade(addDefaults, replaceOverlays) - if outputPath == "-" { - upgradedYaml, err := upgraded.Dump() - if err != nil { - return err - } - fmt.Print(string(upgradedYaml)) - return nil - } else { - if err := util.CopyFile(outputPath, outputPath+"-old"); err != nil { - return err - } - return upgraded.PersistToFile(outputPath) - } + Command.AddCommand(config.Command) + Command.AddCommand(nodes.Command) } diff --git a/internal/app/wwctl/upgrade/config/cobra.go b/internal/app/wwctl/upgrade/config/cobra.go new file mode 100644 index 00000000..a660a32f --- /dev/null +++ b/internal/app/wwctl/upgrade/config/cobra.go @@ -0,0 +1,56 @@ +package config + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/warewulf/warewulf/internal/pkg/config" + libupgrade "github.com/warewulf/warewulf/internal/pkg/upgrade" + "github.com/warewulf/warewulf/internal/pkg/util" +) + +var ( + Command = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "config [OPTIONS]", + Short: "Upgrade an existing nodes.conf", + Long: `Upgrades nodes.conf from a previous version of Warewulf 4 to a format +supported by the current version.`, + RunE: UpgradeNodesConf, + } + + inputPath string + outputPath string +) + +func init() { + Command.Flags().StringVarP(&inputPath, "input-path", "i", config.ConfigFile, "Path to a legacy nodes.conf") + Command.Flags().StringVarP(&outputPath, "output-path", "o", config.ConfigFile, "Path to write the upgraded nodes.conf to") +} + +func UpgradeNodesConf(cmd *cobra.Command, args []string) error { + data, err := os.ReadFile(inputPath) + if err != nil { + return err + } + legacy, err := libupgrade.ParseConfig(data) + if err != nil { + return err + } + upgraded := legacy.Upgrade() + if outputPath == "-" { + upgradedYaml, err := upgraded.Dump() + if err != nil { + return err + } + fmt.Print(string(upgradedYaml)) + return nil + } else { + if err := util.CopyFile(outputPath, outputPath+"-old"); err != nil { + return err + } + return upgraded.PersistToFile(outputPath) + } +} diff --git a/internal/app/wwctl/upgrade/nodes/cobra.go b/internal/app/wwctl/upgrade/nodes/cobra.go new file mode 100644 index 00000000..d138130a --- /dev/null +++ b/internal/app/wwctl/upgrade/nodes/cobra.go @@ -0,0 +1,60 @@ +package nodes + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/warewulf/warewulf/internal/pkg/node" + libupgrade "github.com/warewulf/warewulf/internal/pkg/upgrade" + "github.com/warewulf/warewulf/internal/pkg/util" +) + +var ( + Command = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "nodes [OPTIONS]", + Short: "Upgrade an existing nodes.conf", + Long: `Upgrades nodes.conf from a previous version of Warewulf 4 to a format +supported by the current version.`, + RunE: UpgradeNodesConf, + } + + addDefaults bool + replaceOverlays bool + inputPath string + outputPath string +) + +func init() { + Command.Flags().BoolVar(&addDefaults, "add-defaults", false, "Configure a default profile and set default node values") + Command.Flags().BoolVar(&replaceOverlays, "replace-overlays", false, "Replace 'wwinit' and 'generic' overlays with their split replacements") + Command.Flags().StringVarP(&inputPath, "input-path", "i", node.ConfigFile, "Path to a legacy nodes.conf") + Command.Flags().StringVarP(&outputPath, "output-path", "o", node.ConfigFile, "Path to write the upgraded nodes.conf to") +} + +func UpgradeNodesConf(cmd *cobra.Command, args []string) error { + data, err := os.ReadFile(inputPath) + if err != nil { + return err + } + legacy, err := libupgrade.ParseNodes(data) + if err != nil { + return err + } + upgraded := legacy.Upgrade(addDefaults, replaceOverlays) + if outputPath == "-" { + upgradedYaml, err := upgraded.Dump() + if err != nil { + return err + } + fmt.Print(string(upgradedYaml)) + return nil + } else { + if err := util.CopyFile(outputPath, outputPath+"-old"); err != nil { + return err + } + return upgraded.PersistToFile(outputPath) + } +} diff --git a/internal/pkg/config/buildconfig.go.in b/internal/pkg/config/buildconfig.go.in index 767e254a..2ebc7de4 100644 --- a/internal/pkg/config/buildconfig.go.in +++ b/internal/pkg/config/buildconfig.go.in @@ -26,23 +26,23 @@ const Release = "@RELEASE@" type TFTPConf struct { Enabled bool `yaml:"enabled" default:"true"` - TftpRoot string `yaml:"tftproot" default:"@TFTPDIR@"` - SystemdName string `yaml:"systemd name" default:"tftp"` + TftpRoot string `yaml:"tftproot,omitempty" default:"@TFTPDIR@"` + SystemdName string `yaml:"systemd name,omitempty" default:"tftp"` - IpxeBinaries map[string]string `yaml:"ipxe" default:"{\"00:09\": \"ipxe-snponly-x86_64.efi\",\"00:00\": \"undionly.kpxe\",\"00:0B\": \"arm64-efi/snponly.efi\",\"00:07\": \"ipxe-snponly-x86_64.efi\"}"` + IpxeBinaries map[string]string `yaml:"ipxe,omitempty" default:"{\"00:09\": \"ipxe-snponly-x86_64.efi\",\"00:00\": \"undionly.kpxe\",\"00:0B\": \"arm64-efi/snponly.efi\",\"00:07\": \"ipxe-snponly-x86_64.efi\"}"` } // WarewulfConf adds additional Warewulf-specific configuration to // BaseConf. type WarewulfConf struct { - Port int `yaml:"port" default:"9873"` + Port int `yaml:"port,omitempty" default:"9873"` Secure bool `yaml:"secure" default:"true"` - UpdateInterval int `yaml:"update interval" default:"60"` - AutobuildOverlays bool `yaml:"autobuild overlays" default:"true"` + UpdateInterval int `yaml:"update interval,omitempty" default:"60"` + AutobuildOverlays bool `yaml:"autobuild overlays,omitempty" default:"true"` EnableHostOverlay bool `yaml:"host overlay" default:"true"` - Syslog bool `yaml:"syslog" default:"false"` - DataStore string `yaml:"datastore" default:"@DATADIR@"` - GrubBoot bool `yaml:"grubboot" default:"false"` + Syslog bool `yaml:"syslog,omitempty" default:"false"` + DataStore string `yaml:"datastore,omitempty" default:"@DATADIR@"` + GrubBoot bool `yaml:"grubboot,omitempty" default:"false"` } func (paths BuildConfig) OciBlobCachedir() string { diff --git a/internal/pkg/config/dhcp.go b/internal/pkg/config/dhcp.go index b1b8790e..f496f431 100644 --- a/internal/pkg/config/dhcp.go +++ b/internal/pkg/config/dhcp.go @@ -4,8 +4,8 @@ package config // Warewulf will configure. type DHCPConf struct { Enabled bool `yaml:"enabled" default:"true"` - Template string `yaml:"template" default:"default"` + Template string `yaml:"template,omitempty" default:"default"` RangeStart string `yaml:"range start,omitempty"` RangeEnd string `yaml:"range end,omitempty"` - SystemdName string `yaml:"systemd name" default:"dhcpd"` + SystemdName string `yaml:"systemd name,omitempty" default:"dhcpd"` } diff --git a/internal/pkg/config/nfs.go b/internal/pkg/config/nfs.go index c7ad986e..baaf411d 100644 --- a/internal/pkg/config/nfs.go +++ b/internal/pkg/config/nfs.go @@ -9,16 +9,16 @@ import ( // nodes. type NFSConf struct { Enabled bool `yaml:"enabled" default:"true"` - ExportsExtended []*NFSExportConf `yaml:"export paths" default:"[]"` - SystemdName string `yaml:"systemd name" default:"nfsd"` + ExportsExtended []*NFSExportConf `yaml:"export paths,omitempty" default:"[]"` + SystemdName string `yaml:"systemd name,omitempty" default:"nfsd"` } // An NFSExportConf reprents a single NFS export / mount. type NFSExportConf struct { Path string `yaml:"path" default:"/dev/null"` - ExportOptions string `default:"rw,sync,no_subtree_check" yaml:"export options"` - MountOptions string `default:"defaults" yaml:"mount options"` - Mount bool `default:"true" yaml:"mount"` + ExportOptions string `yaml:"export options,omitempty" default:"rw,sync,no_subtree_check"` + MountOptions string `yaml:"mount options,omitempty" default:"defaults"` + Mount bool `yaml:"mount" default:"true"` } // Implements the Unmarshal interface for NFSConf to set default diff --git a/internal/pkg/config/root.go b/internal/pkg/config/root.go index 8c223707..b8ca89f0 100644 --- a/internal/pkg/config/root.go +++ b/internal/pkg/config/root.go @@ -7,6 +7,7 @@ package config import ( + "bytes" "fmt" "net" "net/netip" @@ -27,19 +28,19 @@ var cachedConf WarewulfYaml // [WarewulfConf], [DHCPConf], [TFTPConf], and [NFSConf] sub-sections. type WarewulfYaml struct { Comment string `yaml:"comment,omitempty"` - Ipaddr string `yaml:"ipaddr"` + Ipaddr string `yaml:"ipaddr,omitempty"` Ipaddr6 string `yaml:"ipaddr6,omitempty"` - Netmask string `yaml:"netmask"` + Netmask string `yaml:"netmask,omitempty"` Network string `yaml:"network,omitempty"` Ipv6net string `yaml:"ipv6net,omitempty"` Fqdn string `yaml:"fqdn,omitempty"` - Warewulf *WarewulfConf `yaml:"warewulf"` - DHCP *DHCPConf `yaml:"dhcp"` - TFTP *TFTPConf `yaml:"tftp"` - NFS *NFSConf `yaml:"nfs"` + Warewulf *WarewulfConf `yaml:"warewulf,omitempty"` + DHCP *DHCPConf `yaml:"dhcp,omitempty"` + TFTP *TFTPConf `yaml:"tftp,omitempty"` + NFS *NFSConf `yaml:"nfs,omitempty"` SSH *SSHConf `yaml:"ssh,omitempty"` - MountsContainer []*MountEntry `yaml:"container mounts" default:"[{\"source\": \"/etc/resolv.conf\", \"dest\": \"/etc/resolv.conf\"}]"` - Paths *BuildConfig `yaml:"paths"` + MountsContainer []*MountEntry `yaml:"container mounts,omitempty" default:"[{\"source\": \"/etc/resolv.conf\", \"dest\": \"/etc/resolv.conf\"}]"` + Paths *BuildConfig `yaml:"paths,omitempty"` WWClient *WWClientConf `yaml:"wwclient,omitempty"` warewulfconf string @@ -206,3 +207,31 @@ func (conf *WarewulfYaml) InitializedFromFile() bool { func (conf *WarewulfYaml) GetWarewulfConf() string { return conf.warewulfconf } + +func (config *WarewulfYaml) Dump() ([]byte, error) { + var buf bytes.Buffer + yamlEncoder := yaml.NewEncoder(&buf) + yamlEncoder.SetIndent(2) + err := yamlEncoder.Encode(config) + return buf.Bytes(), err +} + +func (config *WarewulfYaml) PersistToFile(configFile string) error { + out, dumpErr := config.Dump() + if dumpErr != nil { + wwlog.Error("%s", dumpErr) + return dumpErr + } + file, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) + if err != nil { + wwlog.Error("%s", err) + return err + } + defer file.Close() + _, err = file.WriteString(string(out)) + if err != nil { + return err + } + wwlog.Debug("persisted: %s", configFile) + return nil +} diff --git a/internal/pkg/config/ssh.go b/internal/pkg/config/ssh.go index a9abe4dc..5e08aac7 100644 --- a/internal/pkg/config/ssh.go +++ b/internal/pkg/config/ssh.go @@ -1,5 +1,5 @@ package config type SSHConf struct { - KeyTypes []string `yaml:"key types" default:"[\"rsa\",\"dsa\",\"ecdsa\",\"ed25519\"]"` + KeyTypes []string `yaml:"key types,omitempty" default:"[\"rsa\",\"dsa\",\"ecdsa\",\"ed25519\"]"` } diff --git a/internal/pkg/config/wwclient.go b/internal/pkg/config/wwclient.go index 13993be6..52d92d04 100644 --- a/internal/pkg/config/wwclient.go +++ b/internal/pkg/config/wwclient.go @@ -1,5 +1,5 @@ package config type WWClientConf struct { - Port uint16 `yaml:"port" default:"0"` + Port uint16 `yaml:"port,omitempty" default:"0"` } diff --git a/internal/pkg/upgrade/conf.go b/internal/pkg/upgrade/conf.go deleted file mode 100644 index ddba7018..00000000 --- a/internal/pkg/upgrade/conf.go +++ /dev/null @@ -1,91 +0,0 @@ -package upgrade - -type WarewulfYaml struct { - WWInternal string `yaml:"WW_INTERNAL"` - Comment string `yaml:"comment,omitempty"` - Ipaddr string `yaml:"ipaddr"` - Ipaddr6 string `yaml:"ipaddr6,omitempty"` - Netmask string `yaml:"netmask"` - Network string `yaml:"network,omitempty"` - Ipv6net string `yaml:"ipv6net,omitempty"` - Fqdn string `yaml:"fqdn,omitempty"` - Warewulf *WarewulfConf `yaml:"warewulf"` - DHCP *DHCPConf `yaml:"dhcp"` - TFTP *TFTPConf `yaml:"tftp"` - NFS *NFSConf `yaml:"nfs"` - SSH *SSHConf `yaml:"ssh,omitempty"` - MountsContainer []*MountEntry `yaml:"container mounts"` - Paths *BuildConfig `yaml:"paths"` - WWClient *WWClientConf `yaml:"wwclient"` -} - -type WarewulfConf struct { - Port int `yaml:"port"` - Secure bool `yaml:"secure"` - UpdateInterval int `yaml:"update interval"` - AutobuildOverlays bool `yaml:"autobuild overlays"` - EnableHostOverlay bool `yaml:"host overlay"` - Syslog bool `yaml:"syslog"` - DataStore string `yaml:"datastore"` - GrubBoot bool `yaml:"grubboot"` -} - -type DHCPConf struct { - Enabled bool `yaml:"enabled"` - Template string `yaml:"template"` - RangeStart string `yaml:"range start,omitempty"` - RangeEnd string `yaml:"range end,omitempty"` - SystemdName string `yaml:"systemd name"` -} - -type TFTPConf struct { - Enabled bool `yaml:"enabled"` - TftpRoot string `yaml:"tftproot"` - SystemdName string `yaml:"systemd name"` - - IpxeBinaries map[string]string `yaml:"ipxe"` -} - -type NFSConf struct { - Enabled bool `yaml:"enabled"` - ExportsExtended []*NFSExportConf `yaml:"export paths"` - SystemdName string `yaml:"systemd name"` -} - -type NFSExportConf struct { - Path string `yaml:"path"` - ExportOptions string `yaml:"export options"` - MountOptions string `yaml:"mount options"` - Mount bool `yaml:"mount"` -} - -type SSHConf struct { - KeyTypes []string `yaml:"key types"` -} - -type MountEntry struct { - Source string `yaml:"source"` - Dest string `yaml:"dest,omitempty"` - ReadOnly bool `yaml:"readonly,omitempty"` - Options string `yaml:"options,omitempty"` - Copy bool `yaml:"copy,omitempty"` -} - -type BuildConfig struct { - Bindir string - Sysconfdir string - Localstatedir string - Cachedir string - Ipxesource string - Srvdir string - Firewallddir string - Systemddir string - WWOverlaydir string - WWChrootdir string - WWProvisiondir string - WWClientdir string -} - -type WWClientConf struct { - Port uint16 `yaml:"port"` -} diff --git a/internal/pkg/upgrade/config.go b/internal/pkg/upgrade/config.go new file mode 100644 index 00000000..10476efe --- /dev/null +++ b/internal/pkg/upgrade/config.go @@ -0,0 +1,237 @@ +package upgrade + +import ( + "gopkg.in/yaml.v3" + + "github.com/warewulf/warewulf/internal/pkg/config" +) + +func ParseConfig(data []byte) (warewulfYaml *WarewulfYaml, err error) { + warewulfYaml = new(WarewulfYaml) + if err = yaml.Unmarshal(data, warewulfYaml); err != nil { + return warewulfYaml, err + } + return warewulfYaml, nil +} + +type WarewulfYaml struct { + WWInternal string `yaml:"WW_INTERNAL"` + Comment string `yaml:"comment"` + Ipaddr string `yaml:"ipaddr"` + Ipaddr6 string `yaml:"ipaddr6"` + Netmask string `yaml:"netmask"` + Network string `yaml:"network"` + Ipv6net string `yaml:"ipv6net"` + Fqdn string `yaml:"fqdn"` + Warewulf *WarewulfConf `yaml:"warewulf"` + DHCP *DHCPConf `yaml:"dhcp"` + TFTP *TFTPConf `yaml:"tftp"` + NFS *NFSConf `yaml:"nfs"` + SSH *SSHConf `yaml:"ssh"` + MountsContainer []*MountEntry `yaml:"container mounts"` + Paths *BuildConfig `yaml:"paths"` + WWClient *WWClientConf `yaml:"wwclient"` +} + +func (this *WarewulfYaml) Upgrade() (upgraded *config.WarewulfYaml) { + upgraded = new(config.WarewulfYaml) + if this.WWInternal != "" { + logIgnore("WW_INTERNAL", this.WWInternal, "obsolete") + } + upgraded.Comment = this.Comment + upgraded.Ipaddr = this.Ipaddr + upgraded.Ipaddr6 = this.Ipaddr6 + upgraded.Netmask = this.Netmask + upgraded.Network = this.Network + upgraded.Ipv6net = this.Ipv6net + upgraded.Fqdn = this.Fqdn + if this.Warewulf != nil { + upgraded.Warewulf = this.Warewulf.Upgrade() + } + if this.DHCP != nil { + upgraded.DHCP = this.DHCP.Upgrade() + } + if this.TFTP != nil { + upgraded.TFTP = this.TFTP.Upgrade() + } + if this.NFS != nil { + upgraded.NFS = this.NFS.Upgrade() + } + if this.SSH != nil { + upgraded.SSH = this.SSH.Upgrade() + } + upgraded.MountsContainer = make([]*config.MountEntry, 0) + for _, mount := range this.MountsContainer { + upgraded.MountsContainer = append(upgraded.MountsContainer, mount.Upgrade()) + } + if this.Paths != nil { + upgraded.Paths = this.Paths.Upgrade() + } + if this.WWClient != nil { + upgraded.WWClient = this.WWClient.Upgrade() + } + return upgraded +} + +type WarewulfConf struct { + Port int `yaml:"port"` + Secure bool `yaml:"secure"` + UpdateInterval int `yaml:"update interval"` + AutobuildOverlays bool `yaml:"autobuild overlays"` + EnableHostOverlay bool `yaml:"host overlay"` + Syslog bool `yaml:"syslog"` + DataStore string `yaml:"datastore"` + GrubBoot bool `yaml:"grubboot"` +} + +func (this *WarewulfConf) Upgrade() (upgraded *config.WarewulfConf) { + upgraded = new(config.WarewulfConf) + upgraded.Port = this.Port + upgraded.Secure = this.Secure + upgraded.UpdateInterval = this.UpdateInterval + upgraded.AutobuildOverlays = this.AutobuildOverlays + upgraded.EnableHostOverlay = this.EnableHostOverlay + upgraded.Syslog = this.Syslog + upgraded.DataStore = this.DataStore + upgraded.GrubBoot = this.GrubBoot + return upgraded +} + +type DHCPConf struct { + Enabled bool `yaml:"enabled"` + Template string `yaml:"template"` + RangeStart string `yaml:"range start"` + RangeEnd string `yaml:"range end"` + SystemdName string `yaml:"systemd name"` +} + +func (this *DHCPConf) Upgrade() (upgraded *config.DHCPConf) { + upgraded = new(config.DHCPConf) + upgraded.Enabled = this.Enabled + upgraded.Template = this.Template + upgraded.RangeStart = this.RangeStart + upgraded.RangeEnd = this.RangeEnd + upgraded.SystemdName = this.SystemdName + return upgraded +} + +type TFTPConf struct { + Enabled bool `yaml:"enabled"` + TftpRoot string `yaml:"tftproot"` + SystemdName string `yaml:"systemd name"` + IpxeBinaries map[string]string `yaml:"ipxe"` +} + +func (this *TFTPConf) Upgrade() (upgraded *config.TFTPConf) { + upgraded = new(config.TFTPConf) + upgraded.Enabled = this.Enabled + upgraded.TftpRoot = this.TftpRoot + upgraded.SystemdName = this.SystemdName + upgraded.IpxeBinaries = make(map[string]string) + for name, binary := range this.IpxeBinaries { + upgraded.IpxeBinaries[name] = binary + } + return upgraded +} + +type NFSConf struct { + Enabled bool `yaml:"enabled"` + ExportsExtended []*NFSExportConf `yaml:"export paths"` + SystemdName string `yaml:"systemd name"` +} + +func (this *NFSConf) Upgrade() (upgraded *config.NFSConf) { + upgraded = new(config.NFSConf) + upgraded.Enabled = this.Enabled + upgraded.ExportsExtended = make([]*config.NFSExportConf, 0) + for _, export := range this.ExportsExtended { + upgraded.ExportsExtended = append(upgraded.ExportsExtended, export.Upgrade()) + } + upgraded.SystemdName = this.SystemdName + return upgraded +} + +type NFSExportConf struct { + Path string `yaml:"path"` + ExportOptions string `yaml:"export options"` + MountOptions string `yaml:"mount options"` + Mount bool `yaml:"mount"` +} + +func (this *NFSExportConf) Upgrade() (upgraded *config.NFSExportConf) { + upgraded = new(config.NFSExportConf) + upgraded.Path = this.Path + upgraded.ExportOptions = this.ExportOptions + upgraded.MountOptions = this.MountOptions + upgraded.Mount = this.Mount + return upgraded +} + +type SSHConf struct { + KeyTypes []string `yaml:"key types"` +} + +func (this *SSHConf) Upgrade() (upgraded *config.SSHConf) { + upgraded = new(config.SSHConf) + upgraded.KeyTypes = append([]string{}, this.KeyTypes...) + return upgraded +} + +type MountEntry struct { + Source string `yaml:"source"` + Dest string `yaml:"dest"` + ReadOnly bool `yaml:"readonly"` + Options string `yaml:"options"` + Copy bool `yaml:"copy"` +} + +func (this *MountEntry) Upgrade() (upgraded *config.MountEntry) { + upgraded = new(config.MountEntry) + upgraded.Source = this.Source + upgraded.Dest = this.Dest + upgraded.ReadOnly = this.ReadOnly + upgraded.Options = this.Options + upgraded.Copy = this.Copy + return upgraded +} + +type BuildConfig struct { + Bindir string + Sysconfdir string + Localstatedir string + Cachedir string + Ipxesource string + Srvdir string + Firewallddir string + Systemddir string + WWOverlaydir string + WWChrootdir string + WWProvisiondir string + WWClientdir string +} + +func (this *BuildConfig) Upgrade() (upgraded *config.BuildConfig) { + upgraded = new(config.BuildConfig) + upgraded.Bindir = this.Bindir + upgraded.Sysconfdir = this.Sysconfdir + upgraded.Localstatedir = this.Localstatedir + upgraded.Cachedir = this.Cachedir + upgraded.Ipxesource = this.Ipxesource + upgraded.Srvdir = this.Srvdir + upgraded.Firewallddir = this.Firewallddir + upgraded.WWOverlaydir = this.WWOverlaydir + upgraded.WWChrootdir = this.WWChrootdir + upgraded.WWProvisiondir = this.WWProvisiondir + upgraded.WWClientdir = this.WWClientdir + return upgraded +} + +type WWClientConf struct { + Port uint16 `yaml:"port"` +} + +func (this *WWClientConf) Upgrade() (upgraded *config.WWClientConf) { + upgraded = new(config.WWClientConf) + upgraded.Port = this.Port + return upgraded +} diff --git a/internal/pkg/upgrade/config_test.go b/internal/pkg/upgrade/config_test.go new file mode 100644 index 00000000..50cf474b --- /dev/null +++ b/internal/pkg/upgrade/config_test.go @@ -0,0 +1,415 @@ +package upgrade + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +var configUpgradeTests = []struct { + name string + legacyYaml string + upgradedYaml string +}{ + { + name: "empty", + legacyYaml: ``, + upgradedYaml: `{}`, + }, + { + name: "v4.0.0", + legacyYaml: ` +ipaddr: 192.168.1.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + update interval: 60 +dhcp: + enabled: true + range start: 192.168.1.150 + range end: 192.168.1.200 + template: default + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf +`, + upgradedYaml: ` +ipaddr: 192.168.1.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + update interval: 60 + host overlay: false +dhcp: + enabled: true + template: default + range start: 192.168.1.150 + range end: 192.168.1.200 + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + enabled: false + systemd name: nfs-server +`, + }, + { + name: "v4.1.0", + legacyYaml: ` +ipaddr: 192.168.1.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + update interval: 60 +dhcp: + enabled: true + range start: 192.168.1.150 + range end: 192.168.1.200 + template: default + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf +`, + upgradedYaml: ` +ipaddr: 192.168.1.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + update interval: 60 + host overlay: false +dhcp: + enabled: true + template: default + range start: 192.168.1.150 + range end: 192.168.1.200 + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + enabled: false + systemd name: nfs-server +`, + }, + { + name: "v4.2.0", + legacyYaml: ` +ipaddr: 192.168.200.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + autobuild overlays: true + update interval: 60 + syslog: false +dhcp: + enabled: true + range start: 192.168.200.50 + range end: 192.168.200.99 + template: default + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf +`, + upgradedYaml: ` +ipaddr: 192.168.200.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + update interval: 60 + autobuild overlays: true + host overlay: false +dhcp: + enabled: true + template: default + range start: 192.168.200.50 + range end: 192.168.200.99 + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + enabled: false + systemd name: nfs-server +`, + }, + { + name: "v4.3.0", + legacyYaml: ` +WW_INTERNAL: 43 +ipaddr: 192.168.200.1 +netmask: 255.255.255.0 +network: 192.168.200.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: false + datastore: "" +dhcp: + enabled: true + template: default + range start: 192.168.200.50 + range end: 192.168.200.99 + systemd name: dhcpd +tftp: + enabled: true + tftproot: "" + systemd name: tftp +nfs: + enabled: true + export paths: + - path: /home + export options: rw,sync + mount options: defaults + mount: true + - path: /opt + export options: ro,sync,no_root_squash + mount options: defaults + mount: false + systemd name: nfs-server +`, + upgradedYaml: ` +ipaddr: 192.168.200.1 +netmask: 255.255.255.0 +network: 192.168.200.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true +dhcp: + enabled: true + template: default + range start: 192.168.200.50 + range end: 192.168.200.99 + systemd name: dhcpd +tftp: + enabled: true + systemd name: tftp +nfs: + enabled: true + export paths: + - path: /home + export options: rw,sync + mount options: defaults + mount: true + - path: /opt + export options: ro,sync,no_root_squash + mount options: defaults + mount: false + systemd name: nfs-server +`, + }, + { + name: "v4.4.1", + legacyYaml: ` +WW_INTERNAL: 43 +ipaddr: 192.168.200.1 +netmask: 255.255.255.0 +network: 192.168.200.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: false +dhcp: + enabled: true + range start: 192.168.200.50 + range end: 192.168.200.99 + systemd name: dhcpd +tftp: + enabled: true + systemd name: tftp +nfs: + enabled: true + export paths: + - path: /home + export options: rw,sync + mount options: defaults + mount: true + - path: /opt + export options: ro,sync,no_root_squash + mount options: defaults + mount: false + systemd name: nfs-server +`, + upgradedYaml: ` +ipaddr: 192.168.200.1 +netmask: 255.255.255.0 +network: 192.168.200.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true +dhcp: + enabled: true + range start: 192.168.200.50 + range end: 192.168.200.99 + systemd name: dhcpd +tftp: + enabled: true + systemd name: tftp +nfs: + enabled: true + export paths: + - path: /home + export options: rw,sync + mount options: defaults + mount: true + - path: /opt + export options: ro,sync,no_root_squash + mount options: defaults + mount: false + systemd name: nfs-server +`, + }, + { + name: "v4.5.8", + legacyYaml: ` +WW_INTERNAL: 45 +ipaddr: 10.0.0.1 +netmask: 255.255.252.0 +network: 10.0.0.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: false +dhcp: + enabled: true + range start: 10.0.1.1 + range end: 10.0.1.255 + systemd name: dhcpd +tftp: + enabled: true + systemd name: tftp + ipxe: + 00:09: ipxe-snponly-x86_64.efi + 00:00: undionly.kpxe + 00:0B: arm64-efi/snponly.efi + 00:07: ipxe-snponly-x86_64.efi +nfs: + enabled: true + export paths: + - path: /home + export options: rw,sync + mount options: defaults + mount: true + - path: /opt + export options: ro,sync,no_root_squash + mount options: defaults + mount: false + systemd name: nfs-server +container mounts: + - source: /etc/resolv.conf + dest: /etc/resolv.conf + readonly: true +ssh: + key types: + - rsa + - dsa + - ecdsa + - ed25519 +`, + upgradedYaml: ` +ipaddr: 10.0.0.1 +netmask: 255.255.252.0 +network: 10.0.0.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true +dhcp: + enabled: true + range start: 10.0.1.1 + range end: 10.0.1.255 + systemd name: dhcpd +tftp: + enabled: true + systemd name: tftp + ipxe: + 00:0B: arm64-efi/snponly.efi + "00:00": undionly.kpxe + "00:07": ipxe-snponly-x86_64.efi + "00:09": ipxe-snponly-x86_64.efi +nfs: + enabled: true + export paths: + - path: /home + export options: rw,sync + mount options: defaults + mount: true + - path: /opt + export options: ro,sync,no_root_squash + mount options: defaults + mount: false + systemd name: nfs-server +ssh: + key types: + - rsa + - dsa + - ecdsa + - ed25519 +container mounts: + - source: /etc/resolv.conf + dest: /etc/resolv.conf + readonly: true +`, + }, +} + +func Test_UpgradeConfig(t *testing.T) { + for _, tt := range configUpgradeTests { + t.Run(tt.name, func(t *testing.T) { + legacy, err := ParseConfig([]byte(tt.legacyYaml)) + assert.NoError(t, err) + upgraded := legacy.Upgrade() + upgradedYaml, err := upgraded.Dump() + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(tt.upgradedYaml), strings.TrimSpace(string(upgradedYaml))) + }) + } +} diff --git a/internal/pkg/upgrade/logging.go b/internal/pkg/upgrade/logging.go new file mode 100644 index 00000000..68b1e01d --- /dev/null +++ b/internal/pkg/upgrade/logging.go @@ -0,0 +1,15 @@ +package upgrade + +import ( + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func logIgnore(name string, value interface{}, reason string) { + wwlog.Warn("ignore: %s: %v (%s)", name, value, reason) +} + +func warnError(err error) { + if err != nil { + wwlog.Warn("%s", err) + } +} diff --git a/internal/pkg/upgrade/node.go b/internal/pkg/upgrade/node.go index f237da48..d02e1a5f 100644 --- a/internal/pkg/upgrade/node.go +++ b/internal/pkg/upgrade/node.go @@ -35,33 +35,7 @@ var genericSplitOverlays = []string{ "syncuser", } -func indexOf[T comparable](slice []T, item T) int { - for i, v := range slice { - if v == item { - return i - } - } - return -1 -} - -func replaceSliceElement[T any](original []T, index int, replacement []T) []T { - if index < 0 || index >= len(original) { - return original - } - return append(original[:index], append(replacement, original[index+1:]...)...) -} - -func logIgnore(name string, value interface{}, reason string) { - wwlog.Warn("ignore: %s: %v (%s)", name, value, reason) -} - -func warnError(err error) { - if err != nil { - wwlog.Warn("%s", err) - } -} - -func Parse(data []byte) (nodesYaml *NodesYaml, err error) { +func ParseNodes(data []byte) (nodesYaml *NodesYaml, err error) { nodesYaml = new(NodesYaml) if err = yaml.Unmarshal(data, nodesYaml); err != nil { return nodesYaml, err diff --git a/internal/pkg/upgrade/node_test.go b/internal/pkg/upgrade/node_test.go index cfd98117..1f28f563 100644 --- a/internal/pkg/upgrade/node_test.go +++ b/internal/pkg/upgrade/node_test.go @@ -689,7 +689,7 @@ nodes: func Test_UpgradeNodesYaml(t *testing.T) { for _, tt := range nodesYamlUpgradeTests { t.Run(tt.name, func(t *testing.T) { - legacy, err := Parse([]byte(tt.legacyYaml)) + legacy, err := ParseNodes([]byte(tt.legacyYaml)) assert.NoError(t, err) upgraded := legacy.Upgrade(tt.addDefaults, tt.replaceOverlays) upgradedYaml, err := upgraded.Dump() diff --git a/internal/pkg/upgrade/slices.go b/internal/pkg/upgrade/slices.go new file mode 100644 index 00000000..780350d3 --- /dev/null +++ b/internal/pkg/upgrade/slices.go @@ -0,0 +1,17 @@ +package upgrade + +func indexOf[T comparable](slice []T, item T) int { + for i, v := range slice { + if v == item { + return i + } + } + return -1 +} + +func replaceSliceElement[T any](original []T, index int, replacement []T) []T { + if index < 0 || index >= len(original) { + return original + } + return append(original[:index], append(replacement, original[index+1:]...)...) +}