From 73f34d0099dbef352dc3d6b4fc8eb2556e78da0b Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Wed, 5 Feb 2025 17:14:40 -0700 Subject: [PATCH] Restore server network auto-detection Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 1 + internal/app/wwclient/root.go | 6 +- internal/app/wwctl/configure/main.go | 8 +++ internal/app/wwctl/overlay/imprt/main_test.go | 2 +- internal/app/wwctl/root.go | 2 +- internal/pkg/config/root.go | 38 ++++++++++++- internal/pkg/config/root_test.go | 4 +- internal/pkg/config/util.go | 57 +++++++++++++++++++ internal/pkg/testenv/testenv.go | 2 +- overlays/fstab/internal/fstab_test.go | 2 +- overlays/host/internal/host_test.go | 2 +- overlays/hosts/internal/hosts_test.go | 2 +- overlays/localtime/internal/localtime_test.go | 2 +- 13 files changed, 113 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e5d86f..b9456a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add OPTIONS argument for `warewulfd.service`. #1707 - Document `warewulf.conf:dhcp.template`. #1701 - New template field `IpCIDR`. #1700 +- `wwctl configure` persists auto-detected server network settings to `warewulf.conf`. #1700 ### Changed diff --git a/internal/app/wwclient/root.go b/internal/app/wwclient/root.go index 8034deb2..c1c75b79 100644 --- a/internal/app/wwclient/root.go +++ b/internal/app/wwclient/root.go @@ -54,11 +54,11 @@ func GetRootCommand() *cobra.Command { func CobraRunE(cmd *cobra.Command, args []string) (err error) { conf := warewulfconf.Get() if WarewulfConfArg != "" { - err = conf.Read(WarewulfConfArg) + err = conf.Read(WarewulfConfArg, false) } else if os.Getenv("WAREWULFCONF") != "" { - err = conf.Read(os.Getenv("WAREWULFCONF")) + err = conf.Read(os.Getenv("WAREWULFCONF"), false) } else { - err = conf.Read(warewulfconf.ConfigFile) + err = conf.Read(warewulfconf.ConfigFile, false) } if err != nil { return diff --git a/internal/app/wwctl/configure/main.go b/internal/app/wwctl/configure/main.go index fad8c4af..090a2353 100644 --- a/internal/app/wwctl/configure/main.go +++ b/internal/app/wwctl/configure/main.go @@ -11,6 +11,14 @@ import ( func CobraRunE(cmd *cobra.Command, args []string) error { var err error + + conf := warewulfconf.Get() + if conf.Autodetected() && conf.InitializedFromFile() { + if err = conf.PersistToFile(conf.GetWarewulfConf()); err != nil { + wwlog.Warn("error when persisting auto-detected settings: %s", err) + } + } + if allFunctions { err = configure.DHCP() if err != nil { diff --git a/internal/app/wwctl/overlay/imprt/main_test.go b/internal/app/wwctl/overlay/imprt/main_test.go index b78ea764..2003ce93 100644 --- a/internal/app/wwctl/overlay/imprt/main_test.go +++ b/internal/app/wwctl/overlay/imprt/main_test.go @@ -52,7 +52,7 @@ nodes: {} conf_yml := `` conf := warewulfconf.New() - err = conf.Parse([]byte(conf_yml)) + err = conf.Parse([]byte(conf_yml), false) assert.NoError(t, err) warewulfd.SetNoDaemon() conf.Paths.WWOverlaydir = overlayDir diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go index cfbeeedf..1f9c05bc 100644 --- a/internal/app/wwctl/root.go +++ b/internal/app/wwctl/root.go @@ -87,7 +87,7 @@ func rootPersistentPreRunE(cmd *cobra.Command, args []string) (err error) { conf := warewulfconf.Get() if !AllowEmptyConf && !conf.InitializedFromFile() { - if err = conf.Read(warewulfconf.ConfigFile); err != nil { + if err = conf.Read(warewulfconf.ConfigFile, true); err != nil { wwlog.Error("error reading config file: %s", err) return } diff --git a/internal/pkg/config/root.go b/internal/pkg/config/root.go index b41c7db5..a79a0b57 100644 --- a/internal/pkg/config/root.go +++ b/internal/pkg/config/root.go @@ -41,6 +41,7 @@ type WarewulfYaml struct { WWClient *WWClientConf `yaml:"wwclient,omitempty"` warewulfconf string + autodetected bool } // New caches and returns a new [WarewulfYaml] initialized with empty @@ -73,12 +74,12 @@ func Get() *WarewulfYaml { // Read populates [WarewulfYaml] with the values from a configuration // file. -func (conf *WarewulfYaml) Read(confFileName string) error { +func (conf *WarewulfYaml) Read(confFileName string, autodetect bool) error { wwlog.Debug("Reading warewulf.conf from: %s", confFileName) conf.warewulfconf = confFileName if data, err := os.ReadFile(confFileName); err != nil { return err - } else if err := conf.Parse(data); err != nil { + } else if err := conf.Parse(data, autodetect); err != nil { return err } else { return nil @@ -86,7 +87,7 @@ func (conf *WarewulfYaml) Read(confFileName string) error { } // Parse populates [WarewulfYaml] with the values from a yaml document. -func (conf *WarewulfYaml) Parse(data []byte) error { +func (conf *WarewulfYaml) Parse(data []byte, autodetect bool) error { // ipxe binaries are merged not overwritten, store defaults separate defIpxe := make(map[string]string) for k, v := range conf.TFTP.IpxeBinaries { @@ -110,6 +111,33 @@ func (conf *WarewulfYaml) Parse(data []byte) error { } } + if autodetect { + if conf.Ipaddr == "" { + if ip := GetOutboundIP(); ip != nil { + conf.Ipaddr = ip.String() + conf.autodetected = true + } + } + + if conf.Netmask == "" { + if ip := net.ParseIP(conf.Ipaddr); ip != nil { + if network, err := GetIPNetForIP(ip); err == nil { + conf.Netmask = net.IP(network.Mask).String() + conf.autodetected = true + } + } + } + + if conf.Network == "" { + if ip := net.ParseIP(conf.Ipaddr); ip != nil { + if mask := net.IPMask(net.ParseIP(conf.Netmask)); mask != nil { + conf.Network = ip.Mask(mask).String() + conf.autodetected = true + } + } + } + } + if conf.Ipaddr6 != "" { if _, network, err := net.ParseCIDR(conf.Ipaddr6); err == nil { if conf.Ipv6net == "" { @@ -161,6 +189,10 @@ func (conf *WarewulfYaml) GetWarewulfConf() string { return conf.warewulfconf } +func (conf *WarewulfYaml) Autodetected() bool { + return conf.autodetected +} + func (config *WarewulfYaml) Dump() ([]byte, error) { var buf bytes.Buffer yamlEncoder := yaml.NewEncoder(&buf) diff --git a/internal/pkg/config/root_test.go b/internal/pkg/config/root_test.go index 533002be..9794af2c 100644 --- a/internal/pkg/config/root_test.go +++ b/internal/pkg/config/root_test.go @@ -385,7 +385,7 @@ tftp: for name, tt := range tests { t.Run(name, func(t *testing.T) { conf := New() - err := conf.Parse([]byte(tt.input)) + err := conf.Parse([]byte(tt.input), false) assert.NoError(t, err) result, err := conf.Dump() assert.NoError(t, err) @@ -405,7 +405,7 @@ func TestInitializedFromFile(t *testing.T) { conf := New() assert.False(t, conf.InitializedFromFile()) - assert.NoError(t, conf.Read(tempWarewulfConf.Name())) + assert.NoError(t, conf.Read(tempWarewulfConf.Name(), false)) assert.True(t, conf.InitializedFromFile()) assert.Equal(t, conf.GetWarewulfConf(), tempWarewulfConf.Name()) } diff --git a/internal/pkg/config/util.go b/internal/pkg/config/util.go index 1198d46b..9e74d9cd 100644 --- a/internal/pkg/config/util.go +++ b/internal/pkg/config/util.go @@ -1,5 +1,62 @@ package config +import ( + "fmt" + "net" +) + func BoolP(p *bool) bool { return p != nil && *p } + +func GetOutboundIP() net.IP { + conn, err := net.Dial("udp", "192.0.2.1:80") + if err != nil { + return nil + } + defer conn.Close() + localAddr := conn.LocalAddr().(*net.UDPAddr) + return localAddr.IP +} + +func GetIPNetForIP(ip net.IP) (*net.IPNet, error) { + interfaces, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("failed to get network interfaces: %v", err) + } + + for _, iface := range interfaces { + // skip interfaces that are down or not relevant + if iface.Flags&net.FlagUp == 0 { + continue + } + + addrs, err := iface.Addrs() + if err != nil { + continue // try next interface + } + + for _, addr := range addrs { + // We expect addr to be of type *net.IPNet. + var ipNet *net.IPNet + switch v := addr.(type) { + case *net.IPNet: + ipNet = v + case *net.IPAddr: + // Wrap net.IPAddr in an IPNet with its default mask. + ipNet = &net.IPNet{IP: v.IP, Mask: v.IP.DefaultMask()} + } + + if ipNet == nil { + continue + } + + // Check if the IP matches (for IPv4, Equal works well) + if ipNet.IP.Equal(ip) { + return ipNet, nil + } + } + } + + return nil, fmt.Errorf("could not find IPNet for IP %v", ip) +} diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go index 8a612c11..d6dca4c2 100644 --- a/internal/pkg/testenv/testenv.go +++ b/internal/pkg/testenv/testenv.go @@ -92,7 +92,7 @@ func (env *TestEnv) init() { func (env *TestEnv) Configure() *config.WarewulfYaml { conf := config.New() - err := conf.Read(env.GetPath(path.Join(Sysconfdir, "warewulf/warewulf.conf"))) + err := conf.Read(env.GetPath(path.Join(Sysconfdir, "warewulf/warewulf.conf")), false) env.assertNoError(err) conf.Paths.Sysconfdir = env.GetPath(Sysconfdir) conf.Paths.Bindir = env.GetPath(Bindir) diff --git a/overlays/fstab/internal/fstab_test.go b/overlays/fstab/internal/fstab_test.go index 35050e62..dfbaa0b1 100644 --- a/overlays/fstab/internal/fstab_test.go +++ b/overlays/fstab/internal/fstab_test.go @@ -16,7 +16,7 @@ func Test_fstabOverlay(t *testing.T) { defer env.RemoveAll() env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf") env.ImportFile("etc/warewulf/warewulf.conf", "warewulf.conf") - assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"))) + assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"), false)) env.ImportFile("var/lib/warewulf/overlays/fstab/rootfs/etc/fstab.ww", "../rootfs/etc/fstab.ww") tests := []struct { diff --git a/overlays/host/internal/host_test.go b/overlays/host/internal/host_test.go index 5478a363..6556a176 100644 --- a/overlays/host/internal/host_test.go +++ b/overlays/host/internal/host_test.go @@ -86,7 +86,7 @@ func Test_hostOverlay(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.conf != "" { env.ImportFile("etc/warewulf/warewulf.conf", tt.conf) - assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"))) + assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"), false)) } cmd := show.GetCommand() cmd.SetArgs(tt.args) diff --git a/overlays/hosts/internal/hosts_test.go b/overlays/hosts/internal/hosts_test.go index bf0eb0d2..da26cd68 100644 --- a/overlays/hosts/internal/hosts_test.go +++ b/overlays/hosts/internal/hosts_test.go @@ -19,7 +19,7 @@ func Test_hostsOverlay(t *testing.T) { env := testenv.New(t) defer env.RemoveAll() env.ImportFile("etc/warewulf/warewulf.conf", "warewulf.conf") - assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"))) + assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"), false)) env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf") env.ImportFile("var/lib/warewulf/overlays/hosts/rootfs/etc/hosts.ww", "../rootfs/etc/hosts.ww") diff --git a/overlays/localtime/internal/localtime_test.go b/overlays/localtime/internal/localtime_test.go index 30407248..28c247c2 100644 --- a/overlays/localtime/internal/localtime_test.go +++ b/overlays/localtime/internal/localtime_test.go @@ -15,7 +15,7 @@ func Test_localtimeOverlay(t *testing.T) { env := testenv.New(t) defer env.RemoveAll() env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf") - assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"))) + assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"), false)) env.ImportFile("var/lib/warewulf/overlays/localtime/rootfs/etc/localtime.ww", "../rootfs/etc/localtime.ww") tests := []struct {