From 053dd9953ad41ba8a880b32e68b4942724a78c63 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Mon, 15 Jul 2024 16:27:49 -0600 Subject: [PATCH] Split overlays into discrete functionality and test - Closes #987 To make the overlays testable in isolation, and after discussion with the TSC, removed host/time/source from overlay files. Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 1 + Makefile | 9 +- docs/man/man5/defaults.conf.5 | 17 +- internal/app/wwctl/node/list/main_test.go | 24 +- internal/app/wwctl/overlay/show/main.go | 43 ++- internal/app/wwctl/overlay/show/main_test.go | 32 +- internal/pkg/node/constructors.go | 17 +- internal/pkg/overlay/overlay.go | 2 +- internal/pkg/testenv/testenv.go | 13 +- internal/pkg/wwlog/wwlog.go | 2 +- .../internal/networkmanager_test.go | 112 ++++++ overlays/NetworkManager/internal/nodes.conf | 58 +++ .../conf.d/ww4-discard-configuration.conf | 0 .../NetworkManager/conf.d/ww4-unmanaged.ww | 6 - .../system-connections/ww4-managed.ww | 6 - .../internal/debian_interfaces_test.go | 73 ++++ .../debian.interfaces/internal/nodes.conf | 58 +++ .../rootfs/etc/network/interfaces | 0 .../etc/network/interfaces.d/default.ww | 6 - overlays/debug/internal/debug_test.go | 292 ++++++++++++++ overlays/debug/internal/nodes.conf | 58 +++ .../rootfs/warewulf/template-variables.md.ww | 6 +- overlays/fstab/internal/fstab_test.go | 68 ++++ overlays/fstab/internal/nodes.conf | 58 +++ overlays/fstab/internal/warewulf.conf | 28 ++ .../{wwinit => fstab}/rootfs/etc/fstab.ww | 3 - overlays/generic/rootfs/etc/hosts.ww | 17 - overlays/host/internal/host_test.go | 359 ++++++++++++++++++ overlays/host/internal/nodes.conf | 58 +++ overlays/host/internal/warewulf.conf | 28 ++ overlays/host/internal/warewulf.conf-static | 27 ++ overlays/host/rootfs/etc/dhcp/dhcpd.conf.ww | 3 - .../rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww | 3 - overlays/host/rootfs/etc/exports.ww | 3 - overlays/host/rootfs/etc/hosts.ww | 11 +- overlays/hostname/internal/hostname_test.go | 54 +++ overlays/hostname/internal/nodes.conf | 58 +++ .../rootfs/etc/hostname.ww | 0 overlays/hosts/internal/hosts_test.go | 70 ++++ overlays/hosts/internal/nodes.conf | 58 +++ overlays/hosts/internal/warewulf.conf | 28 ++ overlays/hosts/rootfs/etc/hosts.ww | 14 + overlays/ifcfg/internal/ifcfg_test.go | 103 +++++ overlays/ifcfg/internal/nodes.conf | 58 +++ .../etc/sysconfig/network-scripts/ifcfg.ww | 6 - .../rootfs/etc/sysconfig/network.ww | 0 overlays/ignition/internal/ignition_test.go | 120 ++++++ overlays/ignition/internal/nodes.conf | 58 +++ .../systemd/system/ignition-ww4-disks.service | 0 .../ignition-ww4-disks.service | 0 .../local-fs.target.wants/ww4-disks.target | 0 .../etc/systemd/system/ww4-disks.target.ww | 3 - .../rootfs/etc/systemd/system/ww4-mounts.ww | 6 - .../rootfs/warewulf/ignition.json.ww | 0 overlays/issue/internal/issue_test.go | 64 ++++ overlays/issue/internal/nodes.conf | 58 +++ .../{wwinit => issue}/rootfs/etc/issue.ww | 0 overlays/resolv/internal/nodes.conf | 58 +++ overlays/resolv/internal/resolv_test.go | 56 +++ .../rootfs/etc/resolv.conf.ww | 6 - .../ssh.authorized_keys/internal/nodes.conf | 58 +++ .../internal/ssh_authorized_keys_test.go | 56 +++ .../rootfs/root/.ssh/authorized_keys.ww | 0 overlays/ssh.host_keys/internal/nodes.conf | 58 +++ .../internal/ssh_host_keys_test.go | 146 +++++++ .../rootfs/etc/ssh/ssh_host_dsa_key.pub.ww | 0 .../rootfs/etc/ssh/ssh_host_dsa_key.ww | 0 .../rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww | 0 .../rootfs/etc/ssh/ssh_host_ecdsa_key.ww | 0 .../etc/ssh/ssh_host_ed25519_key.pub.ww | 0 .../rootfs/etc/ssh/ssh_host_ed25519_key.ww | 0 .../rootfs/etc/ssh/ssh_host_rsa_key.pub.ww | 0 .../rootfs/etc/ssh/ssh_host_rsa_key.ww | 0 overlays/syncuser/internal/nodes.conf | 58 +++ overlays/syncuser/internal/syncuser_test.go | 72 ++++ .../{generic => syncuser}/rootfs/etc/group.ww | 0 .../rootfs/etc/passwd.ww | 0 overlays/systemd.netname/internal/nodes.conf | 58 +++ .../internal/systemd_netname_test.go | 66 ++++ .../systemd/network/10-ww4-netname.link.ww} | 5 +- overlays/udev.netname/internal/nodes.conf | 58 +++ .../internal/udev_netname_test.go | 58 +++ .../etc/udev/rules.d/70-ww4-netname.rules.ww | 5 +- overlays/wicked/internal/nodes.conf | 58 +++ overlays/wicked/internal/wicked_test.go | 118 ++++++ .../rootfs/etc/wicked/ifconfig/ifcfg.xml.ww | 6 - overlays/wwclient/internal/nodes.conf | 58 +++ overlays/wwclient/internal/wwclient_test.go | 61 +++ .../multi-user.target.wants/wwclient.service | 0 .../etc/systemd/system/wwclient.service | 0 .../rootfs/warewulf/init.d/80-wwclient.ww | 3 - overlays/wwinit/internal/nodes.conf | 58 +++ overlays/wwinit/internal/warewulf.conf | 28 ++ overlays/wwinit/internal/wwinit_test.go | 103 +++++ userdocs/contents/configuration.rst | 26 +- userdocs/contents/disks.rst | 2 +- userdocs/contents/initialization.rst | 8 +- userdocs/contents/ipmi.rst | 6 +- userdocs/contents/nodeconfig.rst | 8 +- userdocs/contents/overlays.rst | 149 +++++--- userdocs/contents/provisioning.rst | 4 +- userdocs/contents/setup.rst | 4 +- userdocs/contents/templating.rst | 2 +- userdocs/contents/wwctl.rst | 2 +- .../development-environment-kvm.rst | 4 +- .../development-environment-vbox.rst | 4 +- userdocs/quickstart/debian12.rst | 2 +- userdocs/quickstart/suse15.rst | 2 +- warewulf.spec.in | 20 +- 109 files changed, 3581 insertions(+), 232 deletions(-) create mode 100644 overlays/NetworkManager/internal/networkmanager_test.go create mode 100644 overlays/NetworkManager/internal/nodes.conf rename overlays/{wwinit => NetworkManager}/rootfs/etc/NetworkManager/conf.d/ww4-discard-configuration.conf (100%) rename overlays/{wwinit => NetworkManager}/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww (63%) rename overlays/{wwinit => NetworkManager}/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww (91%) create mode 100644 overlays/debian.interfaces/internal/debian_interfaces_test.go create mode 100644 overlays/debian.interfaces/internal/nodes.conf rename overlays/{wwinit => debian.interfaces}/rootfs/etc/network/interfaces (100%) rename overlays/{wwinit => debian.interfaces}/rootfs/etc/network/interfaces.d/default.ww (75%) create mode 100644 overlays/debug/internal/debug_test.go create mode 100644 overlays/debug/internal/nodes.conf create mode 100644 overlays/fstab/internal/fstab_test.go create mode 100644 overlays/fstab/internal/nodes.conf create mode 100644 overlays/fstab/internal/warewulf.conf rename overlays/{wwinit => fstab}/rootfs/etc/fstab.ww (91%) delete mode 100644 overlays/generic/rootfs/etc/hosts.ww create mode 100644 overlays/host/internal/host_test.go create mode 100644 overlays/host/internal/nodes.conf create mode 100644 overlays/host/internal/warewulf.conf create mode 100644 overlays/host/internal/warewulf.conf-static create mode 100644 overlays/hostname/internal/hostname_test.go create mode 100644 overlays/hostname/internal/nodes.conf rename overlays/{wwinit => hostname}/rootfs/etc/hostname.ww (100%) create mode 100644 overlays/hosts/internal/hosts_test.go create mode 100644 overlays/hosts/internal/nodes.conf create mode 100644 overlays/hosts/internal/warewulf.conf create mode 100644 overlays/hosts/rootfs/etc/hosts.ww create mode 100644 overlays/ifcfg/internal/ifcfg_test.go create mode 100644 overlays/ifcfg/internal/nodes.conf rename overlays/{wwinit => ifcfg}/rootfs/etc/sysconfig/network-scripts/ifcfg.ww (85%) rename overlays/{wwinit => ifcfg}/rootfs/etc/sysconfig/network.ww (100%) create mode 100644 overlays/ignition/internal/ignition_test.go create mode 100644 overlays/ignition/internal/nodes.conf rename overlays/{wwinit => ignition}/rootfs/etc/systemd/system/ignition-ww4-disks.service (100%) rename overlays/{wwinit => ignition}/rootfs/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service (100%) rename overlays/{wwinit => ignition}/rootfs/etc/systemd/system/local-fs.target.wants/ww4-disks.target (100%) rename overlays/{wwinit => ignition}/rootfs/etc/systemd/system/ww4-disks.target.ww (91%) rename overlays/{wwinit => ignition}/rootfs/etc/systemd/system/ww4-mounts.ww (87%) rename overlays/{wwinit => ignition}/rootfs/warewulf/ignition.json.ww (100%) create mode 100644 overlays/issue/internal/issue_test.go create mode 100644 overlays/issue/internal/nodes.conf rename overlays/{wwinit => issue}/rootfs/etc/issue.ww (100%) create mode 100644 overlays/resolv/internal/nodes.conf create mode 100644 overlays/resolv/internal/resolv_test.go rename overlays/{wwinit => resolv}/rootfs/etc/resolv.conf.ww (60%) create mode 100644 overlays/ssh.authorized_keys/internal/nodes.conf create mode 100644 overlays/ssh.authorized_keys/internal/ssh_authorized_keys_test.go rename overlays/{generic => ssh.authorized_keys}/rootfs/root/.ssh/authorized_keys.ww (100%) create mode 100644 overlays/ssh.host_keys/internal/nodes.conf create mode 100644 overlays/ssh.host_keys/internal/ssh_host_keys_test.go rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_dsa_key.pub.ww (100%) rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_dsa_key.ww (100%) rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww (100%) rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_ecdsa_key.ww (100%) rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_ed25519_key.pub.ww (100%) rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_ed25519_key.ww (100%) rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_rsa_key.pub.ww (100%) rename overlays/{wwinit => ssh.host_keys}/rootfs/etc/ssh/ssh_host_rsa_key.ww (100%) create mode 100644 overlays/syncuser/internal/nodes.conf create mode 100644 overlays/syncuser/internal/syncuser_test.go rename overlays/{generic => syncuser}/rootfs/etc/group.ww (100%) rename overlays/{generic => syncuser}/rootfs/etc/passwd.ww (100%) create mode 100644 overlays/systemd.netname/internal/nodes.conf create mode 100644 overlays/systemd.netname/internal/systemd_netname_test.go rename overlays/{wwinit/rootfs/etc/systemd/network/10-persistent-net.link.ww => systemd.netname/rootfs/etc/systemd/network/10-ww4-netname.link.ww} (62%) create mode 100644 overlays/udev.netname/internal/nodes.conf create mode 100644 overlays/udev.netname/internal/udev_netname_test.go rename overlays/{wwinit => udev.netname}/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww (71%) create mode 100644 overlays/wicked/internal/nodes.conf create mode 100644 overlays/wicked/internal/wicked_test.go rename overlays/{wwinit => wicked}/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww (89%) create mode 100644 overlays/wwclient/internal/nodes.conf create mode 100644 overlays/wwclient/internal/wwclient_test.go rename overlays/{wwinit => wwclient}/rootfs/etc/systemd/system/multi-user.target.wants/wwclient.service (100%) rename overlays/{wwinit => wwclient}/rootfs/etc/systemd/system/wwclient.service (100%) rename overlays/{wwinit => wwclient}/rootfs/warewulf/init.d/80-wwclient.ww (77%) create mode 100644 overlays/wwinit/internal/nodes.conf create mode 100644 overlays/wwinit/internal/warewulf.conf create mode 100644 overlays/wwinit/internal/wwinit_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b10f0b01..1ad2b82d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Bump github.com/fatih/color from 1.15.0 to 1.17.0 #1224 - Bump github.com/coreos/ignition/v2 from 2.15.0 to 2.19.0 #1239 - Disable building containers by default when calling `wwctl container copy`. #1378 +- Split wwinit and generic overlays into discrete functionality. #987 ### Fixed diff --git a/Makefile b/Makefile index 1a36a410..587ad889 100644 --- a/Makefile +++ b/Makefile @@ -100,6 +100,7 @@ install: build docs install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR) install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR) install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR) + install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwclient/rootfs/$(WWCLIENTDIR) install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/host/rootfs/$(TFTPDIR)/warewulf/ install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe @@ -127,10 +128,10 @@ install: build docs (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/init chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/wwinit - chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/etc/ssh/ssh* - chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww - chmod 0644 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/etc/ssh/ssh*.pub.ww - chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/warewulf/config.ww + chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/config.ww + chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/ssh.host_keys/rootfs/etc/ssh/ssh* + chmod 0644 $(DESTDIR)$(WWOVERLAYDIR)/ssh.host_keys/rootfs/etc/ssh/ssh*.pub.ww + chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww chmod 0750 $(DESTDIR)$(WWOVERLAYDIR)/host/rootfs install -m 0755 wwctl $(DESTDIR)$(BINDIR) install -m 0755 wwclient $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/wwclient diff --git a/docs/man/man5/defaults.conf.5 b/docs/man/man5/defaults.conf.5 index 3abe7257..86096585 100644 --- a/docs/man/man5/defaults.conf.5 +++ b/docs/man/man5/defaults.conf.5 @@ -18,9 +18,24 @@ structure of a node within \fBnodes.pp\fP. .EX defaultnode: runtime overlay: - - generic + - 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 diff --git a/internal/app/wwctl/node/list/main_test.go b/internal/app/wwctl/node/list/main_test.go index 58ef36bf..6832d083 100644 --- a/internal/app/wwctl/node/list/main_test.go +++ b/internal/app/wwctl/node/list/main_test.go @@ -145,8 +145,8 @@ nodes: n01 Id -- n01 n01 Comment default profilecomment n01 Ipxe -- (default) -n01 RuntimeOverlay -- (generic) -n01 SystemOverlay -- (wwinit) +n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser) +n01 SystemOverlay -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition) n01 Root -- (initramfs) n01 Init -- (/sbin/init) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) @@ -168,8 +168,8 @@ nodes: n01 Id -- n01 n01 Comment SUPERSEDED nodecomment n01 Ipxe -- (default) -n01 RuntimeOverlay -- (generic) -n01 SystemOverlay -- (wwinit) +n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser) +n01 SystemOverlay -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition) n01 Root -- (initramfs) n01 Init -- (/sbin/init) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) @@ -251,8 +251,8 @@ nodes: stdout: `NODE FIELD PROFILE VALUE n01 Id -- n01 n01 Ipxe -- (default) -n01 RuntimeOverlay -- (generic) -n01 SystemOverlay -- (wwinit) +n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser) +n01 SystemOverlay -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition) n01 Root -- (initramfs) n01 Init -- (/sbin/init) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) @@ -273,12 +273,14 @@ nodes: name: "multiple overlays list", args: []string{"-l"}, stdout: `NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) -n01 -- -- (wwinit)/rop1,rop2 +n01 -- -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition)/rop1,rop2 `}, { inDb: `WW_INTERNAL: 45 nodeprofiles: p1: + system overlay: + - sop1 runtime overlay: - rop1 - rop2 @@ -293,12 +295,14 @@ nodes: name: "multiple overlays list", args: []string{"-l"}, stdout: `NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) -n01 -- -- (wwinit)/rop2,nop1 ~{rop1} +n01 -- -- sop1/rop2,nop1 ~{rop1} `}, { inDb: `WW_INTERNAL: 45 nodeprofiles: p1: + system overlay: + - sop1 runtime overlay: - rop1 - rop2 @@ -316,7 +320,7 @@ nodes: n01 Id -- n01 n01 Ipxe -- (default) n01 RuntimeOverlay SUPERSEDED rop2,nop1 ~{rop1} -n01 SystemOverlay -- (wwinit) +n01 SystemOverlay p1 sop1 n01 Root -- (initramfs) n01 Init -- (/sbin/init) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) @@ -340,7 +344,7 @@ nodes: stdout: `NODE FIELD PROFILE VALUE n01 Id -- n01 n01 Ipxe -- (default) -n01 RuntimeOverlay -- (generic) +n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser) n01 SystemOverlay SUPERSEDED profileinit,nodeinit n01 Root -- (initramfs) n01 Init -- (/sbin/init) diff --git a/internal/app/wwctl/overlay/show/main.go b/internal/app/wwctl/overlay/show/main.go index d3c2337a..c61566be 100644 --- a/internal/app/wwctl/overlay/show/main.go +++ b/internal/app/wwctl/overlay/show/main.go @@ -3,6 +3,7 @@ package show import ( "bufio" "bytes" + "errors" "os" "path" "path/filepath" @@ -24,30 +25,33 @@ func CobraRunE(cmd *cobra.Command, args []string) error { overlaySourceDir = overlay.OverlaySourceDir(overlayName) if !util.IsDir(overlaySourceDir) { - wwlog.Error("Overlay does not exist: %s", overlayName) - os.Exit(1) + err := errors.New("Overlay does not exist") + wwlog.Error("%s: %s", err, overlayName) + return err } overlayFile := path.Join(overlaySourceDir, fileName) if !util.IsFile(overlayFile) { - wwlog.Error("File does not exist within overlay: %s:%s", overlayName, fileName) - os.Exit(1) + err := errors.New("File does not exist within overlay") + wwlog.Error("%s: %s:%s", err, overlayName, fileName) + return err } if NodeName == "" { f, err := os.ReadFile(overlayFile) if err != nil { wwlog.Error("Could not read file: %s", err) - os.Exit(1) + return err } - wwlog.Info(string(f)) + wwlog.Output(string(f)) } else { if !util.IsFile(overlayFile) { wwlog.Debug("%s is not a file", overlayFile) - wwlog.Error("%s:%s is not a file", overlayName, fileName) - os.Exit(1) + err := errors.New("Not a file") + wwlog.Error("%s: %s:%s", err, overlayName, fileName) + return err } if filepath.Ext(overlayFile) != ".ww" { wwlog.Warn("%s lacks the '.ww' suffix, will not be rendered in an overlay", fileName) @@ -56,12 +60,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { nodeDB, err := node.New() if err != nil { wwlog.Error("Could not open node configuration: %s", err) - os.Exit(1) + return err } nodes, err := nodeDB.FindAllNodes() if err != nil { wwlog.Error("Could not get node list: %s", err) - os.Exit(1) + return err } filteredNodes := node.FilterByName(nodes, []string{NodeName}) if hostName, err := os.Hostname(); err != nil { @@ -73,8 +77,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error { hostNodeInfo.ClusterName.Set(hostName) filteredNodes = append(filteredNodes, *hostNodeInfo) } else if len(filteredNodes) != 1 { - wwlog.Error("%v does not identify a single node", NodeName) - os.Exit(1) + err := errors.New("Not a single node") + wwlog.Error("%s: %v", err, NodeName) + return err } tstruct := overlay.InitStruct(&filteredNodes[0]) @@ -97,10 +102,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Debug("Found multifile comment, new filename %s", filenameFromTemplate[0][1]) if foundFileComment { if !Quiet { - wwlog.Info("backupFile: %v\nwriteFile: %v", backupFile, writeFile) - wwlog.Info("Filename: %s\n", destFileName) + wwlog.Info("backupFile: %v", backupFile) + wwlog.Info("writeFile: %v", writeFile) + wwlog.Info("Filename: %s", destFileName) } - wwlog.Info("%s", outBuffer.String()) + wwlog.Output(outBuffer.String()) outBuffer.Reset() } destFileName = filenameFromTemplate[0][1] @@ -110,10 +116,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } } if !Quiet { - wwlog.Info("backupFile: %v\nwriteFile: %v", backupFile, writeFile) - wwlog.Info("Filename: %s\n", destFileName) + wwlog.Info("backupFile: %v", backupFile) + wwlog.Info("writeFile: %v", writeFile) + wwlog.Info("Filename: %s", destFileName) } - wwlog.Info("%s", outBuffer.String()) + wwlog.Output("%s", outBuffer.String()) } return nil } diff --git a/internal/app/wwctl/overlay/show/main_test.go b/internal/app/wwctl/overlay/show/main_test.go index edca7005..a72dc74d 100644 --- a/internal/app/wwctl/overlay/show/main_test.go +++ b/internal/app/wwctl/overlay/show/main_test.go @@ -7,10 +7,9 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/warewulf/warewulf/internal/pkg/wwlog" - "github.com/warewulf/warewulf/internal/pkg/testenv" "github.com/warewulf/warewulf/internal/pkg/warewulfd" + "github.com/warewulf/warewulf/internal/pkg/wwlog" ) var ( @@ -24,6 +23,35 @@ overlay name {{ .Overlay }} func Test_Overlay_List(t *testing.T) { env := testenv.New(t) + env.WriteFile(t, "etc/warewulf/warewulf.conf", `WW_INTERNAL: 43 +ipaddr: 192.168.0.1/24 +netmask: 255.255.255.0 +network: 192.168.0.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: true +dhcp: + enabled: true + range start: 192.168.0.100 + range end: 192.168.0.199 +tftp: + enabled: false +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`) + env.WriteFile(t, "etc/warewulf/nodes.conf", `WW_INTERNAL: 45 nodeprofiles: diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index c0580388..7cc575a7 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -23,9 +23,24 @@ var ( var FallBackConf = `--- defaultnode: runtime overlay: - - generic + - 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 diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index a49c8bb7..138b034e 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -26,7 +26,7 @@ var ( ) /* -Build all overlays (runtime and generic) for a node +Build all overlays for a node */ func BuildAllOverlays(nodes []node.NodeInfo) error { for _, n := range nodes { diff --git a/internal/pkg/testenv/testenv.go b/internal/pkg/testenv/testenv.go index 0b80bad3..2ec4cd6c 100644 --- a/internal/pkg/testenv/testenv.go +++ b/internal/pkg/testenv/testenv.go @@ -41,7 +41,6 @@ const Systemddir = "usr/lib/systemd/system" const WWOverlaydir = "var/lib/warewulf/overlays" const WWChrootdir = "var/lib/warewulf/chroots" const WWProvisiondir = "srv/warewulf" -const WWClientdir = "warewulf" // New creates a test environment in a temporary directory and configures // Warewulf to use it. @@ -78,7 +77,7 @@ func New(t *testing.T) (env *TestEnv) { conf.Paths.WWOverlaydir = env.GetPath(WWOverlaydir) conf.Paths.WWChrootdir = env.GetPath(WWChrootdir) conf.Paths.WWProvisiondir = env.GetPath(WWProvisiondir) - conf.Paths.WWClientdir = env.GetPath(WWClientdir) + conf.Paths.WWClientdir = "/warewulf" for _, confPath := range []string{ conf.Paths.Sysconfdir, @@ -92,7 +91,6 @@ func New(t *testing.T) (env *TestEnv) { conf.Paths.WWOverlaydir, conf.Paths.WWChrootdir, conf.Paths.WWProvisiondir, - conf.Paths.WWClientdir, } { env.MkdirAll(t, confPath) } @@ -133,6 +131,15 @@ func (env *TestEnv) WriteFile(t *testing.T, fileName string, content string) { assert.NoError(t, err) } +// ImportFile writes the contents of inputFileName to fileName, +// creating any necessary intermediate directories relative to the +// test environment. +func (env *TestEnv) ImportFile(t *testing.T, fileName string, inputFileName string) { + buffer, err := os.ReadFile(inputFileName) + assert.NoError(t, err) + env.WriteFile(t, fileName, string(buffer)) +} + // ReadFile returns the content of fileName as converted to a // string. // diff --git a/internal/pkg/wwlog/wwlog.go b/internal/pkg/wwlog/wwlog.go index 0b8ee5ca..5f71f170 100644 --- a/internal/pkg/wwlog/wwlog.go +++ b/internal/pkg/wwlog/wwlog.go @@ -117,7 +117,7 @@ func DefaultFormatter(logLevel int, rec *LogRecord) string { } } - if rec.Level == INFO && logLevel == INFO { + if (rec.Level == INFO || rec.Level == OUT) && logLevel == INFO { // NOTE: this is a bit strange, but for user-friendliness it makes sense // to not pollute the messages unless something bad happens (by default). // if logLevel > INFO, then level == INFO messages should not be printed anyway, diff --git a/overlays/NetworkManager/internal/networkmanager_test.go b/overlays/NetworkManager/internal/networkmanager_test.go new file mode 100644 index 00000000..9a5d9b19 --- /dev/null +++ b/overlays/NetworkManager/internal/networkmanager_test.go @@ -0,0 +1,112 @@ +package networkmanager + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_networkmanagerOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/NetworkManager/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww", "../rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww", "../rootfs/etc/NetworkManager/system-connections/ww4-managed.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "NetworkManager:ww4-unmanaged.ww", + args: []string{"--render", "node1", "NetworkManager", "etc/NetworkManager/conf.d/ww4-unmanaged.ww"}, + log: networkmanager_unmanaged, + }, + { + name: "NetworkManager:ww4-managed.ww", + args: []string{"--render", "node1", "NetworkManager", "etc/NetworkManager/system-connections/ww4-managed.ww"}, + log: networkmanager_managed, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const networkmanager_unmanaged string = `backupFile: true +writeFile: true +Filename: warewulf-unmanaged.conf +# This file is autogenerated by warewulf +[main] +plugins=keyfile + +[keyfile] +unmanaged-devices=except:interface-name:wwnet0,except:interface-name:wwnet1, +` + +const networkmanager_managed string = `backupFile: true +writeFile: true +Filename: warewulf-default.conf +# This file is autogenerated by warewulf +[connection] +id=default +interface-name=wwnet0 + +type=ethernet +autoconnect=true +[ethernet] +mac-address=e6:92:39:49:7b:03 +# bond +[ipv4] +address=192.168.3.21/24 +gateway=192.168.3.1 +method=manual + +[ipv6] +addr-gen-mode=stable-privacy +method=ignore +never-default=true +backupFile: true +writeFile: true +Filename: warewulf-secondary.conf +# This file is autogenerated by warewulf +[connection] +id=secondary +interface-name=wwnet1 + +type=ethernet +autoconnect=true +[ethernet] +mac-address=9a:77:29:73:14:f1 +# bond +[ipv4] +address=192.168.3.22/24 +gateway=192.168.3.1 +method=manual +dns=8.8.8.8;8.8.4.4; + +[ipv6] +addr-gen-mode=stable-privacy +method=ignore +never-default=true +` diff --git a/overlays/NetworkManager/internal/nodes.conf b/overlays/NetworkManager/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/NetworkManager/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wwinit/rootfs/etc/NetworkManager/conf.d/ww4-discard-configuration.conf b/overlays/NetworkManager/rootfs/etc/NetworkManager/conf.d/ww4-discard-configuration.conf similarity index 100% rename from overlays/wwinit/rootfs/etc/NetworkManager/conf.d/ww4-discard-configuration.conf rename to overlays/NetworkManager/rootfs/etc/NetworkManager/conf.d/ww4-discard-configuration.conf diff --git a/overlays/wwinit/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww b/overlays/NetworkManager/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww similarity index 63% rename from overlays/wwinit/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww rename to overlays/NetworkManager/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww index 3acf2e6c..d77b8ece 100644 --- a/overlays/wwinit/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww +++ b/overlays/NetworkManager/rootfs/etc/NetworkManager/conf.d/ww4-unmanaged.ww @@ -1,12 +1,6 @@ -{{- $host := .BuildHost }} -{{- $time := .BuildTime }} -{{- $source := .BuildSource }} {{- $filename := print "warewulf-unmanaged.conf" }} {{- file $filename }} # This file is autogenerated by warewulf -# Host: {{ $host }} -# Time: {{ $time }} -# Source: {{ $source }} [main] plugins=keyfile diff --git a/overlays/wwinit/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww b/overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww similarity index 91% rename from overlays/wwinit/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww rename to overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww index 8c77a93b..cb7e2bc6 100644 --- a/overlays/wwinit/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww +++ b/overlays/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww @@ -1,13 +1,7 @@ -{{- $host := .BuildHost }} -{{- $time := .BuildTime }} -{{- $source := .BuildSource }} {{- range $connection_id, $netdev := .NetDevs }} {{- $filename := print "warewulf-" $connection_id ".conf" }} {{- file $filename }} # This file is autogenerated by warewulf -# Host: {{ $host }} -# Time: {{ $time }} -# Source: {{ $source }} [connection] id={{ $connection_id }} interface-name={{ $netdev.Device }} diff --git a/overlays/debian.interfaces/internal/debian_interfaces_test.go b/overlays/debian.interfaces/internal/debian_interfaces_test.go new file mode 100644 index 00000000..b2d70cb8 --- /dev/null +++ b/overlays/debian.interfaces/internal/debian_interfaces_test.go @@ -0,0 +1,73 @@ +package debian_interfaces + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_wickedOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/debian.interfaces/rootfs/etc/network/interfaces.d/default.ww", "../rootfs/etc/network/interfaces.d/default.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "debian.interfaces", + args: []string{"--render", "node1", "debian.interfaces", "etc/network/interfaces.d/default.ww"}, + log: debian_interfaces, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const debian_interfaces string = `backupFile: true +writeFile: true +Filename: default +# This file is autogenerated by warewulf +auto wwnet0 +allow-hotplug wwnet0 +iface wwnet0 inet static + address 192.168.3.21 + netmask 255.255.255.0 + gateway 192.168.3.1 + mtu +backupFile: true +writeFile: true +Filename: secondary +# This file is autogenerated by warewulf +auto wwnet1 +allow-hotplug wwnet1 +iface wwnet1 inet static + address 192.168.3.22 + netmask 255.255.255.0 + gateway 192.168.3.1 + mtu + up ifmetric wwnet1 30 +` diff --git a/overlays/debian.interfaces/internal/nodes.conf b/overlays/debian.interfaces/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/debian.interfaces/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wwinit/rootfs/etc/network/interfaces b/overlays/debian.interfaces/rootfs/etc/network/interfaces similarity index 100% rename from overlays/wwinit/rootfs/etc/network/interfaces rename to overlays/debian.interfaces/rootfs/etc/network/interfaces diff --git a/overlays/wwinit/rootfs/etc/network/interfaces.d/default.ww b/overlays/debian.interfaces/rootfs/etc/network/interfaces.d/default.ww similarity index 75% rename from overlays/wwinit/rootfs/etc/network/interfaces.d/default.ww rename to overlays/debian.interfaces/rootfs/etc/network/interfaces.d/default.ww index 1981837b..77ec1fdd 100644 --- a/overlays/wwinit/rootfs/etc/network/interfaces.d/default.ww +++ b/overlays/debian.interfaces/rootfs/etc/network/interfaces.d/default.ww @@ -1,13 +1,7 @@ -{{- $host := .BuildHost }} -{{- $time := .BuildTime }} -{{- $source := .BuildSource }} {{range $devname, $netdev := .ThisNode.NetDevs -}} {{- $filename := print $devname }} {{- file $filename }} # This file is autogenerated by warewulf -# Host: {{ $host }} -# Time: {{ $time }} -# Source: {{ $source }} {{ if $netdev.OnBoot.GetB -}} auto {{$netdev.Device.Get}} {{ end -}} diff --git a/overlays/debug/internal/debug_test.go b/overlays/debug/internal/debug_test.go new file mode 100644 index 00000000..44c502d1 --- /dev/null +++ b/overlays/debug/internal/debug_test.go @@ -0,0 +1,292 @@ +package debug + +import ( + "bytes" + "os" + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_debugOverlay(t *testing.T) { + variableData := regexp.MustCompile(`(?m)(BuildTime|BuildTimeUnix|BuildSource|DataStore):.*$`) + hostname, _ := os.Hostname() + + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/debug/rootfs/warewulf/template-variables.md.ww", "../rootfs/warewulf/template-variables.md.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "debug", + args: []string{"--render", "node1", "debug", "warewulf/template-variables.md.ww"}, + log: debug, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, strings.Replace(tt.log, "%HOSTNAME%", hostname, -1), variableData.ReplaceAllString(logbuf.String(), "${1}: REMOVED_BY_TEST")) + }) + } +} + +const debug = `backupFile: true +writeFile: true +Filename: warewulf/template-variables.md +# Warewulf template variables + +This Warewulf template serves as a complete example of the variables +available to Warewulf templates. It may also be rendered against a +node to debug its apparent configuration. + + wwctl overlay show --render $nodename debug /warewulf/template-variables.md.ww + +The template data structure is defined in +internal/pkg/overlay/datastructure.go, though it also references +data from other structures. + + +## Node + +- Id: node1 +- Hostname: node1 +- Comment: +- ClusterName: +- ContainerName: rockylinux-9 +- Ipxe: default +- RuntimeOverlay: hosts,ssh.authorized_keys,syncuser +- SystemOverlay: wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition +- Init: /sbin/init +- Root: initramfs +- AssetKey: +- Discoverable: +- Profiles: empty +- Tags: +- TagsDel: +- Keys: +- Kernel: + - Version: + - Override: + - Args: quiet crashkernel=no vga=791 net.naming-scheme=v238 +- Ipmi: + - UserName: user + - Password: password + - Ipaddr: 192.168.4.21 + - Netmask: 255.255.255.0 + - Port: + - Gateway: 192.168.4.1 + - Interface: + - Write: true + - Tags: + - TagsDel: +- NetDevs[default]: + - Type: ethernet + - OnBoot: true + - Device: wwnet0 + - Hwaddr: e6:92:39:49:7b:03 + - Ipaddr: 192.168.3.21 + - IpCIDR: 192.168.3.21/24 + - Ipaddr6: + - Prefix: + - Netmask: 255.255.255.0 + - Gateway: 192.168.3.1 + - MTU: + - Primary: true + - Default: + - Tags: + - TagsDel: +- NetDevs[secondary]: + - Type: ethernet + - OnBoot: true + - Device: wwnet1 + - Hwaddr: 9a:77:29:73:14:f1 + - Ipaddr: 192.168.3.22 + - IpCIDR: 192.168.3.22/24 + - Ipaddr6: + - Prefix: + - Netmask: 255.255.255.0 + - Gateway: 192.168.3.1 + - MTU: + - Primary: + - Default: + - Tags: DNS1=8.8.8.8 DNS2=8.8.4.4 + - TagsDel: + + +## Build variables + +- BuildHost: %HOSTNAME% +- BuildTime: REMOVED_BY_TEST +- BuildTimeUnix: REMOVED_BY_TEST +- BuildSource: REMOVED_BY_TEST +- Overlay: debug + + +## Network + +- Ipaddr: +- Ipaddr6: +- Netmask: +- Network: +- NetworkCIDR: +- Ipv6: false + + +## Services + +### DHCP + +- Dhcp.Enabled: true +- Dhcp.Template: default +- Dhcp.RangeStart: +- Dhcp.RangeEnd: +- Dhcp.SystemdName: dhcpd + + +### NFS + +- Enabled: true +- SystemdName: nfsd + +### SSH +- Key types: + - rsa + - dsa + - ecdsa + - ed25519 +- First key type: rsa + +### Warewulf + +- Port: 9983 +- Secure: true +- UpdateInterval: 60 +- AutobuildOverlays: true +- EnableHostOverlay: true +- Syslog: false +- DataStore: REMOVED_BY_TEST + + +### Other nodes + +- AllNodes[0]: + - Id: node1 + - Comment: + - ClusterName: + - ContainerName: rockylinux-9 + - Ipxe: default + - RuntimeOverlay: hosts + - SystemOverlay: wwinit + - Root: initramfs + - Discoverable: + - Init: /sbin/init + - AssetKey: + - Profiles: empty + - Tags: + - Kernel + - Override: + - Args: quiet crashkernel=no vga=791 net.naming-scheme=v238 + - Ipmi: + - Ipaddr: 192.168.4.21 + - Netmask: 255.255.255.0 + - Port: + - Gateway: 192.168.4.1 + - UserName: user + - Password: password + - Interface: + - Write: true + - Tags: + - NetDevs[default]: + - Type: ethernet + - OnBoot: true + - Device: wwnet0 + - Hwaddr: e6:92:39:49:7b:03 + - Ipaddr: 192.168.3.21 + - Ipaddr6: + - IpCIDR: + - Prefix: + - Netmask: 255.255.255.0 + - Gateway: 192.168.3.1 + - MTU: + - Primary: true + - Tags: + - NetDevs[secondary]: + - Type: ethernet + - OnBoot: true + - Device: wwnet1 + - Hwaddr: 9a:77:29:73:14:f1 + - Ipaddr: 192.168.3.22 + - Ipaddr6: + - IpCIDR: + - Prefix: + - Netmask: 255.255.255.0 + - Gateway: 192.168.3.1 + - MTU: + - Primary: + - Tags: DNS1=8.8.8.8 DNS2=8.8.4.4 +- AllNodes[1]: + - Id: node2 + - Comment: + - ClusterName: + - ContainerName: + - Ipxe: default + - RuntimeOverlay: hosts + - SystemOverlay: wwinit + - Root: initramfs + - Discoverable: + - Init: /sbin/init + - AssetKey: + - Profiles: empty + - Tags: + - Kernel + - Override: + - Args: quiet crashkernel=no vga=791 net.naming-scheme=v238 + - Ipmi: + - Ipaddr: + - Netmask: + - Port: + - Gateway: + - UserName: + - Password: + - Interface: + - Write: + - Tags: + - NetDevs[default]: + - Type: ethernet + - OnBoot: true + - Device: wwnet0 + - Hwaddr: e6:92:39:49:7b:04 + - Ipaddr: 192.168.3.23 + - Ipaddr6: + - IpCIDR: + - Prefix: + - Netmask: 255.255.255.0 + - Gateway: 192.168.3.1 + - MTU: + - Primary: true + - Tags: + +` diff --git a/overlays/debug/internal/nodes.conf b/overlays/debug/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/debug/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/debug/rootfs/warewulf/template-variables.md.ww b/overlays/debug/rootfs/warewulf/template-variables.md.ww index 8b2e96cd..dcd4708a 100644 --- a/overlays/debug/rootfs/warewulf/template-variables.md.ww +++ b/overlays/debug/rootfs/warewulf/template-variables.md.ww @@ -4,12 +4,10 @@ This Warewulf template serves as a complete example of the variables available to Warewulf templates. It may also be rendered against a node to debug its apparent configuration. -```sh -wwctl overlay show --render $nodename debug /warewulf/template-variables.md.ww -``` + wwctl overlay show --render $nodename debug /warewulf/template-variables.md.ww The template data structure is defined in -`internal/pkg/overlay/datastructure.go`, though it also references +internal/pkg/overlay/datastructure.go, though it also references data from other structures. diff --git a/overlays/fstab/internal/fstab_test.go b/overlays/fstab/internal/fstab_test.go new file mode 100644 index 00000000..54afd014 --- /dev/null +++ b/overlays/fstab/internal/fstab_test.go @@ -0,0 +1,68 @@ +package fstab + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/config" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_fstabOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "etc/warewulf/warewulf.conf", "warewulf.conf") + assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"))) + env.ImportFile(t, "var/lib/warewulf/overlays/fstab/rootfs/etc/fstab.ww", "../rootfs/etc/fstab.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "/etc/fstab", + args: []string{"--render", "node1", "fstab", "etc/fstab.ww"}, + log: fstab, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const fstab string = `backupFile: true +writeFile: true +Filename: etc/fstab +# This file is autogenerated by warewulf +rootfs / tmpfs defaults 0 0 +devpts /dev/pts devpts gid=5,mode=620 0 0 +tmpfs /run/shm tmpfs defaults 0 0 +sysfs /sys sysfs defaults 0 0 +proc /proc proc defaults 0 0 +# mounts for local file systems created with ignition in nodes.conf +# all with noauto as mounts happens with systemd units +/dev/disk/by-partlabel/scratch /scratch btrfs noauto,defaults 0 0 +/dev/disk/by-partlabel/swap swap swap noauto,defaults 0 0 +# nfs mounts provided in warewulf.conf +192.168.0.1:/home /home nfs defaults 0 0 +` diff --git a/overlays/fstab/internal/nodes.conf b/overlays/fstab/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/fstab/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/fstab/internal/warewulf.conf b/overlays/fstab/internal/warewulf.conf new file mode 100644 index 00000000..56d81ac5 --- /dev/null +++ b/overlays/fstab/internal/warewulf.conf @@ -0,0 +1,28 @@ +WW_INTERNAL: 43 +ipaddr: 192.168.0.1/24 +netmask: 255.255.255.0 +network: 192.168.0.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: true +dhcp: + enabled: true + range start: 192.168.0.100 + range end: 192.168.0.199 +tftp: + enabled: false +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 diff --git a/overlays/wwinit/rootfs/etc/fstab.ww b/overlays/fstab/rootfs/etc/fstab.ww similarity index 91% rename from overlays/wwinit/rootfs/etc/fstab.ww rename to overlays/fstab/rootfs/etc/fstab.ww index 555d6b89..63feb6a3 100644 --- a/overlays/wwinit/rootfs/etc/fstab.ww +++ b/overlays/fstab/rootfs/etc/fstab.ww @@ -1,7 +1,4 @@ # This file is autogenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source: {{.BuildSource}} rootfs / tmpfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 tmpfs /run/shm tmpfs defaults 0 0 diff --git a/overlays/generic/rootfs/etc/hosts.ww b/overlays/generic/rootfs/etc/hosts.ww deleted file mode 100644 index 3fe21b65..00000000 --- a/overlays/generic/rootfs/etc/hosts.ww +++ /dev/null @@ -1,17 +0,0 @@ -127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 -::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 - - - -# Warewulf Server -{{$.Ipaddr}} {{$.BuildHost}} warewulf - -{{- range $node := $.AllNodes}} {{/* for each node */}} -# Entry for {{$node.Id.Get}} -{{- range $devname, $netdev := $node.NetDevs}} {{/* for each network device on the node */}} -{{- if $netdev.Ipaddr.Defined}} {{/* if we have an ip address on this network device */}} -{{- /* emit the node name as hostname if this is the primary */}} -{{$netdev.Ipaddr.Get}} {{if $netdev.Primary.GetB}}{{$node.Id.Get}}{{end}} {{$node.Id.Get}}-{{$devname}} {{if $netdev.Device.Defined}}{{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} -{{- end }}{{/* if ip */}} -{{- end }}{{/* for each network device */}} -{{- end }}{{/* for each node */}} diff --git a/overlays/host/internal/host_test.go b/overlays/host/internal/host_test.go new file mode 100644 index 00000000..4d8589ec --- /dev/null +++ b/overlays/host/internal/host_test.go @@ -0,0 +1,359 @@ +package host + +import ( + "bytes" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/config" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_hostOverlay(t *testing.T) { + hostname, _ := os.Hostname() + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/host/rootfs/etc/dhcp/dhcpd.conf.ww", "../rootfs/etc/dhcp/dhcpd.conf.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww", "../rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/host/rootfs/etc/exports.ww", "../rootfs/etc/exports.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/host/rootfs/etc/hosts.ww", "../rootfs/etc/hosts.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/host/rootfs/etc/profile.d/ssh_setup.csh.ww", "../rootfs/etc/profile.d/ssh_setup.csh.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/host/rootfs/etc/profile.d/ssh_setup.sh.ww", "../rootfs/etc/profile.d/ssh_setup.sh.ww") + + tests := []struct { + name string + conf string + args []string + log string + header string + }{ + { + name: "host:dhcp", + conf: "warewulf.conf", + args: []string{"--render", "host", "host", "etc/dhcp/dhcpd.conf.ww"}, + log: host_dhcp, + header: "", + }, + { + name: "host:dhcp(static)", + conf: "warewulf.conf-static", + args: []string{"--render", "host", "host", "etc/dhcp/dhcpd.conf.ww"}, + log: host_dhcp_static, + header: "", + }, + { + name: "host:dnsmasq", + conf: "warewulf.conf", + args: []string{"--render", "host", "host", "etc/dnsmasq.d/ww4-hosts.conf.ww"}, + log: host_dnsmasq, + header: "", + }, + { + name: "host:/etc/exports", + conf: "", + args: []string{"--render", "host", "host", "etc/exports.ww"}, + log: host_exports, + header: "", + }, + { + name: "host:/etc/hosts", + args: []string{"--render", "host", "host", "etc/hosts.ww"}, + log: host_hosts, + header: "# Do not edit after this line", + }, + { + name: "host:ssh_setup.csh", + conf: "", + args: []string{"--render", "host", "host", "etc/profile.d/ssh_setup.csh.ww"}, + log: host_ssh_setup_csh, + header: "", + }, + { + name: "host:ssh_setup.sh", + conf: "", + args: []string{"--render", "host", "host", "etc/profile.d/ssh_setup.sh.ww"}, + log: host_ssh_setup_sh, + header: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.conf != "" { + env.ImportFile(t, "etc/warewulf/warewulf.conf", tt.conf) + assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"))) + } + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + var log string + if tt.header != "" { + log = skipHeader(t, logbuf.String(), tt.header) + } else { + log = logbuf.String() + } + assert.Equal(t, strings.Replace(tt.log, "%HOSTNAME%", hostname, -1), log) + }) + } +} + +func skipHeader(t *testing.T, log string, header string) (newlog string) { + newlog = "" + found := false + for _, line := range strings.SplitAfter(log, "\n") { + if strings.Contains(line, header) { + found = true + } + if found || strings.HasPrefix(line, "backupFile:") || strings.HasPrefix(line, "writeFile:") || strings.HasPrefix(line, "Filename:") { + newlog = newlog + line + } + } + return +} + +const host_dhcp string = `backupFile: true +writeFile: true +Filename: etc/dhcp/dhcpd.conf +# This file is autogenerated by warewulf + +allow booting; +allow bootp; +ddns-update-style interim; +authoritative; + +option space ipxe; + +# Tell iPXE to not wait for ProxyDHCP requests to speed up boot. +option ipxe.no-pxedhcp code 176 = unsigned integer 8; +option ipxe.no-pxedhcp 1; + +option space PXE; +option PXE.mtftp-ip code 1 = ip-address; +option PXE.mtftp-cport code 2 = unsigned integer 16; +option PXE.mtftp-sport code 3 = unsigned integer 16; +option PXE.mtftp-tmout code 4 = unsigned integer 8; +option PXE.mtftp-delay code 5 = unsigned integer 8; + +option architecture-type code 93 = unsigned integer 16; +if exists user-class and option user-class = "iPXE" { + filename "http://192.168.0.1:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}"; +} else { + + if option architecture-type = 00:00 { + filename "/warewulf/undionly.kpxe"; + } + if option architecture-type = 00:07 { + filename "/warewulf/ipxe-snponly-x86_64.efi"; + } + if option architecture-type = 00:09 { + filename "/warewulf/ipxe-snponly-x86_64.efi"; + } + if option architecture-type = 00:0B { + filename "/warewulf/snponly.efi"; + } +} + +subnet 192.168.0.0 netmask 255.255.255.0 { + max-lease-time 120; + range 192.168.0.100 192.168.0.199; + next-server 192.168.0.1; +} +` + +const host_dhcp_static string = `backupFile: true +writeFile: true +Filename: etc/dhcp/dhcpd.conf +# This file is autogenerated by warewulf + +allow booting; +allow bootp; +ddns-update-style interim; +authoritative; + +option space ipxe; + +# Tell iPXE to not wait for ProxyDHCP requests to speed up boot. +option ipxe.no-pxedhcp code 176 = unsigned integer 8; +option ipxe.no-pxedhcp 1; + +option space PXE; +option PXE.mtftp-ip code 1 = ip-address; +option PXE.mtftp-cport code 2 = unsigned integer 16; +option PXE.mtftp-sport code 3 = unsigned integer 16; +option PXE.mtftp-tmout code 4 = unsigned integer 8; +option PXE.mtftp-delay code 5 = unsigned integer 8; + +option architecture-type code 93 = unsigned integer 16; +if exists user-class and option user-class = "iPXE" { + filename "http://192.168.0.1:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}"; +} else { + + if option architecture-type = 00:00 { + filename "/warewulf/undionly.kpxe"; + } + if option architecture-type = 00:07 { + filename "/warewulf/ipxe-snponly-x86_64.efi"; + } + if option architecture-type = 00:09 { + filename "/warewulf/ipxe-snponly-x86_64.efi"; + } + if option architecture-type = 00:0B { + filename "/warewulf/snponly.efi"; + } +} + +subnet 192.168.0.0 netmask 255.255.255.0 { + max-lease-time 120; +} +host node1-default +{ + hardware ethernet e6:92:39:49:7b:03; + fixed-address 192.168.3.21; + option host-name "node1"; +} + +host node1-secondary +{ + hardware ethernet 9a:77:29:73:14:f1; + fixed-address 192.168.3.22; +} + + +host node2-default +{ + hardware ethernet e6:92:39:49:7b:04; + fixed-address 192.168.3.23; + option host-name "node2"; +} + + + +` + +const host_dnsmasq string = `backupFile: false +writeFile: true +Filename: etc/dnsmasq.d/ww4-hosts.conf +# This file was autgenerated by warewulf + +# select the x86 hosts which will get the iXPE binary +dhcp-match=set:bios,option:client-arch,0 #legacy boot +dhcp-match=set:x86PC,option:client-arch, 7 #EFI x86-64 +dhcp-match=set:x86PC,option:client-arch, 6 #EFI x86-64 +dhcp-match=set:x86PC,option:client-arch, 9 #EFI x86-64 +dhcp-match=set:aarch64,option:client-arch, 11 #EFI aarch64 +dhcp-match=set:iPXE,77,"iPXE" +dhcp-userclass=set:iPXE,iPXE +dhcp-vendorclass=set:efi-http,HTTPClient:Arch:00016 +dhcp-option-force=tag:efi-http,60,HTTPClient +# for http boot always use shim/grub +dhcp-boot=tag:efi-http,"http://192.168.0.1:9873/efiboot/shim.efi" +dhcp-boot=tag:x86PC,"/warewulf/ipxe-snponly-x86_64.efi" +dhcp-boot=tag:aarch64,"/warewulf/arm64-efi/snponly.efi" +# iPXE binary will get the following configuration file +dhcp-boot=tag:iPXE,"http://192.168.0.1:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}" +dhcp-no-override +# define the the range +dhcp-range=192.168.0.100,192.168.0.199,255.255.255.0,6h + +dhcp-host=e6:92:39:49:7b:03,set:warewulf,node1,192.168.3.21,infinite +dhcp-host=9a:77:29:73:14:f1,set:warewulf,node1,192.168.3.22,infinite +dhcp-host=e6:92:39:49:7b:04,set:warewulf,node2,192.168.3.23,infinite +` + +const host_exports string = `backupFile: true +writeFile: true +Filename: etc/exports + +# This file is autogenerated by warewulf +/home 192.168.0.0/255.255.255.0(rw,sync) +/opt 192.168.0.0/255.255.255.0(ro,sync,no_root_squash) + +` + +const host_hosts string = `backupFile: true +writeFile: true +Filename: etc/hosts +# Do not edit after this line +# This block is autogenerated by warewulf + + +# Warewulf Server +192.168.0.1 %HOSTNAME% warewulf +# Entry for node1 +192.168.3.21 node1 node1-default node1-wwnet0 +192.168.3.22 node1-secondary node1-wwnet1 +# Entry for node2 +192.168.3.23 node2 node2-default node2-wwnet0 +` + +const host_ssh_setup_csh = `backupFile: true +writeFile: true +Filename: etc/profile.d/ssh_setup.csh +#!/bin/csh + +## Automatically configure SSH keys for a user on C SHell login +## Copy this file to /etc/profile.d along with ssh_setup.sh + +` + "set _UID=`id -u`" + ` +if ( ( $_UID > 500 || $_UID == 0 ) && ( ! -f "$HOME/.ssh/config" && ! -f "$HOME/.ssh/cluster" ) ) then + echo "Configuring SSH for cluster access" + install -d -m 700 $HOME/.ssh + ssh-keygen -t rsa -f $HOME/.ssh/cluster -N '' -C "Warewulf Cluster key" >& /dev/null + cat $HOME/.ssh/cluster.pub >>! $HOME/.ssh/authorized_keys + chmod 0600 $HOME/.ssh/authorized_keys + + touch $HOME/.ssh/config + echo -n "# Added by Warewulf " >>! $HOME/.ssh/config + (date +%Y-%m-%d >> $HOME/.ssh/config) >& /dev/null + echo "Host *" >> $HOME/.ssh/config + echo " IdentityFile ~/.ssh/cluster" >> $HOME/.ssh/config + echo " StrictHostKeyChecking=no" >> $HOME/.ssh/config + chmod 0600 $HOME/.ssh/config +endif +` + +const host_ssh_setup_sh = `backupFile: true +writeFile: true +Filename: etc/profile.d/ssh_setup.sh +#!/bin/sh +## +## Copyright (c) 2001-2003 Gregory M. Kurtzer +## +## Copyright (c) 2003-2012, The Regents of the University of California, +## through Lawrence Berkeley National Laboratory (subject to receipt of any +## required approvals from the U.S. Dept. of Energy). All rights reserved. +## +## Copied from https://github.com/warewulf/warewulf3/blob/master/cluster/bin/cluster-env + +## Automatically configure SSH keys for a user on login +## Copy this file to /etc/profile.d + +` + "_UID=`id -u`" + ` +if [ $_UID -ge 500 -o $_UID -eq 0 ] && [ ! -f "$HOME/.ssh/config" -a ! -f "$HOME/.ssh/cluster" ]; then + echo "Configuring SSH for cluster access" + install -d -m 700 $HOME/.ssh + ssh-keygen -t rsa -f $HOME/.ssh/cluster -N '' -C "Warewulf Cluster key" > /dev/null 2>&1 + cat $HOME/.ssh/cluster.pub >> $HOME/.ssh/authorized_keys + chmod 0600 $HOME/.ssh/authorized_keys + + echo "# Added by Warewulf ` + "`date +%Y-%m-%d 2>/dev/null`" + `" >> $HOME/.ssh/config + echo "Host *" >> $HOME/.ssh/config + echo " IdentityFile ~/.ssh/cluster" >> $HOME/.ssh/config + echo " StrictHostKeyChecking=no" >> $HOME/.ssh/config + chmod 0600 $HOME/.ssh/config +fi +` diff --git a/overlays/host/internal/nodes.conf b/overlays/host/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/host/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/host/internal/warewulf.conf b/overlays/host/internal/warewulf.conf new file mode 100644 index 00000000..56d81ac5 --- /dev/null +++ b/overlays/host/internal/warewulf.conf @@ -0,0 +1,28 @@ +WW_INTERNAL: 43 +ipaddr: 192.168.0.1/24 +netmask: 255.255.255.0 +network: 192.168.0.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: true +dhcp: + enabled: true + range start: 192.168.0.100 + range end: 192.168.0.199 +tftp: + enabled: false +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 diff --git a/overlays/host/internal/warewulf.conf-static b/overlays/host/internal/warewulf.conf-static new file mode 100644 index 00000000..4e3207c5 --- /dev/null +++ b/overlays/host/internal/warewulf.conf-static @@ -0,0 +1,27 @@ +WW_INTERNAL: 43 +ipaddr: 192.168.0.1/24 +netmask: 255.255.255.0 +network: 192.168.0.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: true +dhcp: + enabled: true + template: static +tftp: + enabled: false +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 diff --git a/overlays/host/rootfs/etc/dhcp/dhcpd.conf.ww b/overlays/host/rootfs/etc/dhcp/dhcpd.conf.ww index 4489c748..04282f19 100644 --- a/overlays/host/rootfs/etc/dhcp/dhcpd.conf.ww +++ b/overlays/host/rootfs/etc/dhcp/dhcpd.conf.ww @@ -1,8 +1,5 @@ {{if $.Dhcp.Enabled -}} # This file is autogenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source: {{.BuildSource}} allow booting; allow bootp; diff --git a/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww b/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww index d942faf3..01233e65 100644 --- a/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww +++ b/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww @@ -1,7 +1,4 @@ # This file was autgenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source {{.BuildSource}} {{ nobackup }} # select the x86 hosts which will get the iXPE binary dhcp-match=set:bios,option:client-arch,0 #legacy boot diff --git a/overlays/host/rootfs/etc/exports.ww b/overlays/host/rootfs/etc/exports.ww index 75e280ef..7507c37f 100644 --- a/overlays/host/rootfs/etc/exports.ww +++ b/overlays/host/rootfs/etc/exports.ww @@ -1,8 +1,5 @@ {{- if .Nfs.Enabled }} # This file is autogenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source: {{.BuildSource}} {{- $network := .Network }} {{- $netmask := .Netmask }} {{- range .Nfs.ExportsExtended }} diff --git a/overlays/host/rootfs/etc/hosts.ww b/overlays/host/rootfs/etc/hosts.ww index 92a93378..8b5a4d90 100644 --- a/overlays/host/rootfs/etc/hosts.ww +++ b/overlays/host/rootfs/etc/hosts.ww @@ -1,17 +1,14 @@ {{ IncludeBlock "/etc/hosts" "# Do not edit after this line" }} # This block is autogenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source: {{.BuildSource}} # Warewulf Server {{$.Ipaddr}} {{$.BuildHost}} warewulf -{{- range $node := $.AllNodes}} {{/* for each node */}} -# Entry for {{$node.Id.Get}} -{{- range $devname, $netdev := $node.NetDevs}} {{/* for each network device on the node */}} -{{- if $netdev.Ipaddr.Defined}} {{/* if we have an ip address on this network device */}} +{{- range $node := $.AllNodes }}{{/* for each node */}} +# Entry for {{ $node.Id.Get }} +{{- range $devname, $netdev := $node.NetDevs }}{{/* for each network device on the node */}} +{{- if $netdev.Ipaddr.Defined }}{{/* if we have an ip address on this network device */}} {{- /* emit the node name as hostname if this is the primary */}} {{$netdev.Ipaddr.Get}} {{if $netdev.Primary.GetB}}{{$node.Id.Get}}{{end}} {{$node.Id.Get}}-{{$devname}} {{if $netdev.Device.Defined}}{{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} {{- end }}{{/* if ip */}} diff --git a/overlays/hostname/internal/hostname_test.go b/overlays/hostname/internal/hostname_test.go new file mode 100644 index 00000000..01a18cda --- /dev/null +++ b/overlays/hostname/internal/hostname_test.go @@ -0,0 +1,54 @@ +package hostname + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_hostnameOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/hostname/rootfs/etc/hostname.ww", "../rootfs/etc/hostname.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "/etc/hostname", + args: []string{"--render", "node1", "hostname", "etc/hostname.ww"}, + log: hostname, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const hostname string = `backupFile: true +writeFile: true +Filename: etc/hostname +node1 +` diff --git a/overlays/hostname/internal/nodes.conf b/overlays/hostname/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/hostname/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wwinit/rootfs/etc/hostname.ww b/overlays/hostname/rootfs/etc/hostname.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/hostname.ww rename to overlays/hostname/rootfs/etc/hostname.ww diff --git a/overlays/hosts/internal/hosts_test.go b/overlays/hosts/internal/hosts_test.go new file mode 100644 index 00000000..f0eacda2 --- /dev/null +++ b/overlays/hosts/internal/hosts_test.go @@ -0,0 +1,70 @@ +package hosts + +import ( + "bytes" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/config" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_hostsOverlay(t *testing.T) { + hostname, _ := os.Hostname() + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/warewulf.conf", "warewulf.conf") + assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"))) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/hosts/rootfs/etc/hosts.ww", "../rootfs/etc/hosts.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "/etc/hosts", + args: []string{"--render", "node1", "hosts", "etc/hosts.ww"}, + log: hosts, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, strings.Replace(tt.log, "%HOSTNAME%", hostname, -1), logbuf.String()) + }) + } +} + +const hosts string = `backupFile: true +writeFile: true +Filename: etc/hosts +127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 +::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 + +# Warewulf Server +192.168.0.1 %HOSTNAME% warewulf +# Entry for node1 +192.168.3.21 node1 node1-default node1-wwnet0 +192.168.3.22 node1-secondary node1-wwnet1 +# Entry for node2 +192.168.3.23 node2 node2-default node2-wwnet0 +` diff --git a/overlays/hosts/internal/nodes.conf b/overlays/hosts/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/hosts/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/hosts/internal/warewulf.conf b/overlays/hosts/internal/warewulf.conf new file mode 100644 index 00000000..56d81ac5 --- /dev/null +++ b/overlays/hosts/internal/warewulf.conf @@ -0,0 +1,28 @@ +WW_INTERNAL: 43 +ipaddr: 192.168.0.1/24 +netmask: 255.255.255.0 +network: 192.168.0.0 +warewulf: + port: 9873 + secure: false + update interval: 60 + autobuild overlays: true + host overlay: true + syslog: true +dhcp: + enabled: true + range start: 192.168.0.100 + range end: 192.168.0.199 +tftp: + enabled: false +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 diff --git a/overlays/hosts/rootfs/etc/hosts.ww b/overlays/hosts/rootfs/etc/hosts.ww new file mode 100644 index 00000000..3e20a3b7 --- /dev/null +++ b/overlays/hosts/rootfs/etc/hosts.ww @@ -0,0 +1,14 @@ +127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 +::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 + +# Warewulf Server +{{$.Ipaddr}} {{$.BuildHost}} warewulf + +{{- range $node := $.AllNodes }} +# Entry for {{$node.Id.Get}} +{{- range $devname, $netdev := $node.NetDevs }} +{{- if $netdev.Ipaddr.Defined }} +{{$netdev.Ipaddr.Get}} {{if $netdev.Primary.GetB}}{{$node.Id.Get}}{{end}} {{$node.Id.Get}}-{{$devname}} {{if $netdev.Device.Defined}}{{$node.Id.Get}}-{{$netdev.Device.Get}}{{end}} +{{- end }} +{{- end }} +{{- end }} diff --git a/overlays/ifcfg/internal/ifcfg_test.go b/overlays/ifcfg/internal/ifcfg_test.go new file mode 100644 index 00000000..c0a479b4 --- /dev/null +++ b/overlays/ifcfg/internal/ifcfg_test.go @@ -0,0 +1,103 @@ +package ifcfg + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_ifcfgOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/ifcfg/rootfs/etc/sysconfig/network-scripts/ifcfg.ww", "../rootfs/etc/sysconfig/network-scripts/ifcfg.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ifcfg/rootfs/etc/sysconfig/network.ww", "../rootfs/etc/sysconfig/network.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "ifcfg:ifcfg.ww", + args: []string{"--render", "node1", "ifcfg", "etc/sysconfig/network-scripts/ifcfg.ww"}, + log: ifcfg, + }, + { + name: "ifcfg:network.ww", + args: []string{"--render", "node1", "ifcfg", "etc/sysconfig/network.ww"}, + log: ifcfg_network, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const ifcfg string = `backupFile: true +writeFile: true +Filename: ifcfg-default.conf +# This file is autogenerated by warewulf +TYPE=ethernet +DEVICE=wwnet0 +NAME=default +BOOTPROTO=static +DEVTIMEOUT=10 +IPADDR=192.168.3.21 +NETMASK=255.255.255.0 +GATEWAY=192.168.3.1 +HWADDR=e6:92:39:49:7b:03 +TYPE=ethernet +ONBOOT=true +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +backupFile: true +writeFile: true +Filename: ifcfg-secondary.conf +# This file is autogenerated by warewulf +TYPE=ethernet +DEVICE=wwnet1 +NAME=secondary +BOOTPROTO=static +DEVTIMEOUT=10 +IPADDR=192.168.3.22 +NETMASK=255.255.255.0 +GATEWAY=192.168.3.1 +HWADDR=9a:77:29:73:14:f1 +TYPE=ethernet +ONBOOT=true +IPV6INIT=yes +IPV6_AUTOCONF=yes +IPV6_DEFROUTE=yes +IPV6_FAILURE_FATAL=no +DNS1=8.8.8.8 +DNS2=8.8.4.4 +` + +const ifcfg_network string = `backupFile: true +writeFile: true +Filename: etc/sysconfig/network +NETWORKING=yes +HOSTNAME=node1 +` diff --git a/overlays/ifcfg/internal/nodes.conf b/overlays/ifcfg/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/ifcfg/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wwinit/rootfs/etc/sysconfig/network-scripts/ifcfg.ww b/overlays/ifcfg/rootfs/etc/sysconfig/network-scripts/ifcfg.ww similarity index 85% rename from overlays/wwinit/rootfs/etc/sysconfig/network-scripts/ifcfg.ww rename to overlays/ifcfg/rootfs/etc/sysconfig/network-scripts/ifcfg.ww index 9e5cb983..b841eb4f 100644 --- a/overlays/wwinit/rootfs/etc/sysconfig/network-scripts/ifcfg.ww +++ b/overlays/ifcfg/rootfs/etc/sysconfig/network-scripts/ifcfg.ww @@ -1,13 +1,7 @@ -{{- $host := .BuildHost }} -{{- $time := .BuildTime }} -{{- $source := .BuildSource }} {{range $devname, $netdev := .NetDevs -}} {{- $filename := print "ifcfg-" $devname ".conf" }} {{- file $filename }} # This file is autogenerated by warewulf -# Host: {{ $host }} -# Time: {{ $time }} -# Source: {{ $source }} TYPE={{ $netdev.Type }} DEVICE={{ $netdev.Device }} NAME={{ $devname }} diff --git a/overlays/wwinit/rootfs/etc/sysconfig/network.ww b/overlays/ifcfg/rootfs/etc/sysconfig/network.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/sysconfig/network.ww rename to overlays/ifcfg/rootfs/etc/sysconfig/network.ww diff --git a/overlays/ignition/internal/ignition_test.go b/overlays/ignition/internal/ignition_test.go new file mode 100644 index 00000000..d4919b3c --- /dev/null +++ b/overlays/ignition/internal/ignition_test.go @@ -0,0 +1,120 @@ +package ignition + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_ignitionOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww", "../rootfs/etc/systemd/system/ww4-disks.target.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww", "../rootfs/etc/systemd/system/ww4-mounts.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ignition/rootfs/warewulf/ignition.json.ww", "../rootfs/warewulf/ignition.json.ww") + + tests := []struct { + name string + args []string + log string + json bool + }{ + { + name: "ignition:ww4-disks.target", + args: []string{"--render", "node1", "ignition", "etc/systemd/system/ww4-disks.target.ww"}, + log: ignition_disks, + json: false, + }, + { + name: "ignition:ww4-mounts", + args: []string{"--render", "node1", "ignition", "etc/systemd/system/ww4-mounts.ww"}, + log: ignition_mounts, + json: false, + }, + { + name: "ignition:ignition.json", + args: []string{"--quiet", "--render", "node1", "ignition", "warewulf/ignition.json.ww"}, + log: ignition_json, + json: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + if tt.json { + var expected, actual interface{} + assert.NoError(t, json.Unmarshal([]byte(tt.log), &expected)) + assert.NoError(t, json.Unmarshal(logbuf.Bytes(), &actual)) + assert.True(t, reflect.DeepEqual(expected, actual), "expected: %v\nactual : %v", expected, actual) + } else { + assert.Equal(t, tt.log, logbuf.String()) + } + }) + } +} + +const ignition_disks string = `backupFile: true +writeFile: true +Filename: etc/systemd/system/ww4-disks.target +# This file is autogenerated by warewulf +[Unit] +Description=mount ww4 disks +# make sure that the disks are available +Requires=ignition-ww4-disks.service +After=ignition-ww4-disks.service +Requisite=ignition-ww4-disks.service +# Get the mounts +Wants=scratch.mount +Wants=dev-disk-by\x2dpartlabel-swap.swap +` + +const ignition_mounts string = `backupFile: true +writeFile: true +Filename: scratch.mount + +# This file is autogenerated by warewulf +[Unit] +ConditionPathExists=/warewulf/ignition.json +Before=local-fs.target +After=ignition-ww4-disks.service +[Mount] +Where=/scratch +What=/dev/disk/by-partlabel/scratch +Type=btrfs +[Install] +RequiredBy=local-fs.target +backupFile: true +writeFile: true +Filename: dev-disk-by\x2dpartlabel-swap.swap +# This file is autogenerated by warewulf +[Unit] +ConditionPathExists=/warewulf/ignition.json +After=ignition-ww4-disks.service +Before=swap.target +[Swap] +What=/dev/disk/by-partlabel/swap +[Install] +RequiredBy=swap.target +` + +const ignition_json string = `{"ignition":{"version":"3.1.0"},"storage":{"disks":[{"device":"/dev/vda","partitions":[{"label":"scratch","shouldExist":true,"wipePartitionEntry":false},{"label":"swap","number":1,"shouldExist":false,"sizeMiB":1024,"wipePartitionEntry":false}],"wipeTable":true}],"filesystems":[{"device":"/dev/disk/by-partlabel/scratch","format":"btrfs","path":"/scratch","wipeFilesystem":true},{"device":"/dev/disk/by-partlabel/swap","format":"swap","path":"swap","wipeFilesystem":false}]}} +` diff --git a/overlays/ignition/internal/nodes.conf b/overlays/ignition/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/ignition/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wwinit/rootfs/etc/systemd/system/ignition-ww4-disks.service b/overlays/ignition/rootfs/etc/systemd/system/ignition-ww4-disks.service similarity index 100% rename from overlays/wwinit/rootfs/etc/systemd/system/ignition-ww4-disks.service rename to overlays/ignition/rootfs/etc/systemd/system/ignition-ww4-disks.service diff --git a/overlays/wwinit/rootfs/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service b/overlays/ignition/rootfs/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service similarity index 100% rename from overlays/wwinit/rootfs/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service rename to overlays/ignition/rootfs/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service diff --git a/overlays/wwinit/rootfs/etc/systemd/system/local-fs.target.wants/ww4-disks.target b/overlays/ignition/rootfs/etc/systemd/system/local-fs.target.wants/ww4-disks.target similarity index 100% rename from overlays/wwinit/rootfs/etc/systemd/system/local-fs.target.wants/ww4-disks.target rename to overlays/ignition/rootfs/etc/systemd/system/local-fs.target.wants/ww4-disks.target diff --git a/overlays/wwinit/rootfs/etc/systemd/system/ww4-disks.target.ww b/overlays/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww similarity index 91% rename from overlays/wwinit/rootfs/etc/systemd/system/ww4-disks.target.ww rename to overlays/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww index 3d16437e..13ed0045 100644 --- a/overlays/wwinit/rootfs/etc/systemd/system/ww4-disks.target.ww +++ b/overlays/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww @@ -1,7 +1,4 @@ # This file is autogenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source: {{.BuildSource}} [Unit] Description=mount ww4 disks # make sure that the disks are available diff --git a/overlays/wwinit/rootfs/etc/systemd/system/ww4-mounts.ww b/overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww similarity index 87% rename from overlays/wwinit/rootfs/etc/systemd/system/ww4-mounts.ww rename to overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww index ad318012..371dce7f 100644 --- a/overlays/wwinit/rootfs/etc/systemd/system/ww4-mounts.ww +++ b/overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww @@ -1,6 +1,3 @@ -{{- $host := .BuildHost }} -{{- $time := .BuildTime }} -{{- $source := .BuildSource }} {{- range $fsdevice,$fs := .FileSystems }} {{- $prefix := replace "-" "\\x2d" $fs.Path }} {{- $prefix = replace "/" "-" $prefix }} @@ -11,9 +8,6 @@ {{- $filename := print $prefix "." $suffix }} {{- file $filename }} # This file is autogenerated by warewulf -# Host: {{ $host }} -# Time: {{ $time }} -# Source: {{ $source }} {{- if eq $fs.Format "swap"}} [Unit] ConditionPathExists=/warewulf/ignition.json diff --git a/overlays/wwinit/rootfs/warewulf/ignition.json.ww b/overlays/ignition/rootfs/warewulf/ignition.json.ww similarity index 100% rename from overlays/wwinit/rootfs/warewulf/ignition.json.ww rename to overlays/ignition/rootfs/warewulf/ignition.json.ww diff --git a/overlays/issue/internal/issue_test.go b/overlays/issue/internal/issue_test.go new file mode 100644 index 00000000..1da3f92f --- /dev/null +++ b/overlays/issue/internal/issue_test.go @@ -0,0 +1,64 @@ +package issue + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_issueOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/issue/rootfs/etc/issue.ww", "../rootfs/etc/issue.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "/etc/issue", + args: []string{"--render", "node1", "issue", "etc/issue.ww"}, + log: issue, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const issue string = `backupFile: true +writeFile: true +Filename: etc/issue +Warewulf Node: node1 +Container: rockylinux-9 +Kernelargs: quiet crashkernel=no vga=791 net.naming-scheme=v238 + +Network: + default: wwnet0 + default: 192.168.3.21/24 + default: e6:92:39:49:7b:03 + secondary: wwnet1 + secondary: 192.168.3.22/24 + secondary: 9a:77:29:73:14:f1 +` diff --git a/overlays/issue/internal/nodes.conf b/overlays/issue/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/issue/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wwinit/rootfs/etc/issue.ww b/overlays/issue/rootfs/etc/issue.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/issue.ww rename to overlays/issue/rootfs/etc/issue.ww diff --git a/overlays/resolv/internal/nodes.conf b/overlays/resolv/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/resolv/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/resolv/internal/resolv_test.go b/overlays/resolv/internal/resolv_test.go new file mode 100644 index 00000000..ead935be --- /dev/null +++ b/overlays/resolv/internal/resolv_test.go @@ -0,0 +1,56 @@ +package resolv + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_resolvOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/resolv/rootfs/etc/resolv.conf.ww", "../rootfs/etc/resolv.conf.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "/etc/resolv.conf", + args: []string{"--render", "node1", "resolv", "etc/resolv.conf.ww"}, + log: resolv_conf, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const resolv_conf string = `backupFile: true +writeFile: true +Filename: etc/resolv.conf +# This file is autogenerated by warewulf +nameserver 8.8.8.8 +nameserver 8.8.4.4 +` diff --git a/overlays/wwinit/rootfs/etc/resolv.conf.ww b/overlays/resolv/rootfs/etc/resolv.conf.ww similarity index 60% rename from overlays/wwinit/rootfs/etc/resolv.conf.ww rename to overlays/resolv/rootfs/etc/resolv.conf.ww index aecde584..3fa1946a 100644 --- a/overlays/wwinit/rootfs/etc/resolv.conf.ww +++ b/overlays/resolv/rootfs/etc/resolv.conf.ww @@ -1,10 +1,4 @@ -{{- $host := .BuildHost }} -{{- $time := .BuildTime }} -{{- $source := .BuildSource }} # This file is autogenerated by warewulf -# Host: {{ $host }} -# Time: {{ $time }} -# Source: {{ $source }} {{range $devname, $netdev := .NetDevs -}} {{range $tk, $tv := $netdev.Tags -}} {{ $prefix := substr 0 3 $tk -}} diff --git a/overlays/ssh.authorized_keys/internal/nodes.conf b/overlays/ssh.authorized_keys/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/ssh.authorized_keys/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/ssh.authorized_keys/internal/ssh_authorized_keys_test.go b/overlays/ssh.authorized_keys/internal/ssh_authorized_keys_test.go new file mode 100644 index 00000000..41705946 --- /dev/null +++ b/overlays/ssh.authorized_keys/internal/ssh_authorized_keys_test.go @@ -0,0 +1,56 @@ +package ssh_authorized_keys + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_ssh_authorized_keysOverlay(t *testing.T) { + t.Skip("ssh.authorized_keys is not yet isolated from the host") + + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.authorized_keys/rootfs/root/.ssh/authorized_keys.ww", "../rootfs/root/.ssh/authorized_keys.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "ssh.authorized_keys:authorized_keys.ww", + args: []string{"--render", "node1", "ssh.authorized_keys", "root/.ssh/authorized_keys.ww"}, + log: ssh_authorized_keys, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const ssh_authorized_keys string = `backupFile: true +writeFile: true +Filename: root/.ssh/authorized_keys + +` diff --git a/overlays/generic/rootfs/root/.ssh/authorized_keys.ww b/overlays/ssh.authorized_keys/rootfs/root/.ssh/authorized_keys.ww similarity index 100% rename from overlays/generic/rootfs/root/.ssh/authorized_keys.ww rename to overlays/ssh.authorized_keys/rootfs/root/.ssh/authorized_keys.ww diff --git a/overlays/ssh.host_keys/internal/nodes.conf b/overlays/ssh.host_keys/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/ssh.host_keys/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/ssh.host_keys/internal/ssh_host_keys_test.go b/overlays/ssh.host_keys/internal/ssh_host_keys_test.go new file mode 100644 index 00000000..cc4ea1c4 --- /dev/null +++ b/overlays/ssh.host_keys/internal/ssh_host_keys_test.go @@ -0,0 +1,146 @@ +package ssh_host_keys + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_ssh_host_keysOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_dsa_key.pub.ww", "../rootfs/etc/ssh/ssh_host_dsa_key.pub.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_dsa_key.ww", "../rootfs/etc/ssh/ssh_host_dsa_key.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww", "../rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ecdsa_key.ww", "../rootfs/etc/ssh/ssh_host_ecdsa_key.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ed25519_key.pub.ww", "../rootfs/etc/ssh/ssh_host_ed25519_key.pub.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ed25519_key.ww", "../rootfs/etc/ssh/ssh_host_ed25519_key.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_rsa_key.pub.ww", "../rootfs/etc/ssh/ssh_host_rsa_key.pub.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_rsa_key.ww", "../rootfs/etc/ssh/ssh_host_rsa_key.ww") + env.WriteFile(t, "etc/warewulf/keys/ssh_host_dsa_key.pub", `dsa pubkey sentinel`) + env.WriteFile(t, "etc/warewulf/keys/ssh_host_dsa_key", `dsa key sentinel`) + env.WriteFile(t, "etc/warewulf/keys/ssh_host_ecdsa_key.pub", `ecdsa pubkey sentinel`) + env.WriteFile(t, "etc/warewulf/keys/ssh_host_ecdsa_key", `ecdsa key sentinel`) + env.WriteFile(t, "etc/warewulf/keys/ssh_host_ed25519_key.pub", `ed25519 pubkey sentinel`) + env.WriteFile(t, "etc/warewulf/keys/ssh_host_ed25519_key", `ed25519 key sentinel`) + env.WriteFile(t, "etc/warewulf/keys/ssh_host_rsa_key.pub", `rsa pubkey sentinel`) + env.WriteFile(t, "etc/warewulf/keys/ssh_host_rsa_key", `rsa key sentinel`) + + tests := []struct { + name string + args []string + log string + }{ + { + name: "ssh.host_keys:dsa pub", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_dsa_key.pub.ww"}, + log: ssh_host_dsa_key_pub, + }, + { + name: "ssh.host_keys:dsa", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_dsa_key.ww"}, + log: ssh_host_dsa_key, + }, + { + name: "ssh.host_keys:ecdsa pub", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_ecdsa_key.pub.ww"}, + log: ssh_host_ecdsa_key_pub, + }, + { + name: "ssh.host_keys:ecdsa", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_ecdsa_key.ww"}, + log: ssh_host_ecdsa_key, + }, + { + name: "ssh.host_keys:rsa pub", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_rsa_key.pub.ww"}, + log: ssh_host_rsa_key_pub, + }, + { + name: "ssh.host_keys:dsa", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_rsa_key.ww"}, + log: ssh_host_rsa_key, + }, + { + name: "ssh.host_keys:ed25519 pub", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_ed25519_key.pub.ww"}, + log: ssh_host_ed25519_key_pub, + }, + { + name: "ssh.host_keys:ed25519", + args: []string{"--render", "node1", "ssh.host_keys", "etc/ssh/ssh_host_ed25519_key.ww"}, + log: ssh_host_ed25519_key, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const ssh_host_dsa_key_pub string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_dsa_key.pub +dsa pubkey sentinel +` + +const ssh_host_dsa_key string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_dsa_key +dsa key sentinel +` + +const ssh_host_ecdsa_key_pub string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_ecdsa_key.pub +ecdsa pubkey sentinel +` + +const ssh_host_ecdsa_key string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_ecdsa_key +ecdsa key sentinel +` + +const ssh_host_ed25519_key_pub string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_ed25519_key.pub +ed25519 pubkey sentinel +` + +const ssh_host_ed25519_key string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_ed25519_key +ed25519 key sentinel +` + +const ssh_host_rsa_key_pub string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_rsa_key.pub +rsa pubkey sentinel +` + +const ssh_host_rsa_key string = `backupFile: true +writeFile: true +Filename: etc/ssh/ssh_host_rsa_key +rsa key sentinel +` diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_dsa_key.pub.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_dsa_key.pub.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_dsa_key.pub.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_dsa_key.pub.ww diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_dsa_key.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_dsa_key.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_dsa_key.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_dsa_key.ww diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ecdsa_key.pub.ww diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_ecdsa_key.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ecdsa_key.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_ecdsa_key.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ecdsa_key.ww diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_ed25519_key.pub.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ed25519_key.pub.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_ed25519_key.pub.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ed25519_key.pub.ww diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_ed25519_key.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ed25519_key.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_ed25519_key.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_ed25519_key.ww diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_rsa_key.pub.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_rsa_key.pub.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_rsa_key.pub.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_rsa_key.pub.ww diff --git a/overlays/wwinit/rootfs/etc/ssh/ssh_host_rsa_key.ww b/overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_rsa_key.ww similarity index 100% rename from overlays/wwinit/rootfs/etc/ssh/ssh_host_rsa_key.ww rename to overlays/ssh.host_keys/rootfs/etc/ssh/ssh_host_rsa_key.ww diff --git a/overlays/syncuser/internal/nodes.conf b/overlays/syncuser/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/syncuser/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/syncuser/internal/syncuser_test.go b/overlays/syncuser/internal/syncuser_test.go new file mode 100644 index 00000000..33ecf31c --- /dev/null +++ b/overlays/syncuser/internal/syncuser_test.go @@ -0,0 +1,72 @@ +package syncuser + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_syncuserOverlay(t *testing.T) { + t.Skip("syncuser is not yet isolated from the host") + + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/syncuser/rootfs/etc/passwd.ww", "../../../../../overlays/syncuser/rootfs/etc/passwd.ww") + env.ImportFile(t, "var/lib/warewulf/overlays/syncuser/rootfs/etc/group.ww", "../../../../../overlays/syncuser/rootfs/etc/group.ww") + env.WriteFile(t, "var/lib/warewulf/chroots/rockylinux-9/rootfs/etc/passwd", `root:x:0:0:root:/root:/bin/bash`) + env.WriteFile(t, "var/lib/warewulf/chroots/rockylinux-9/rootfs/etc/group", `root:x:0:`) + + tests := []struct { + name string + args []string + log string + }{ + { + name: "syncuser:passwd.ww", + args: []string{"--render", "node1", "syncuser", "etc/passwd.ww"}, + log: syncuser_passwd, + }, + { + name: "syncuser:group.ww", + args: []string{"--render", "node1", "syncuser", "etc/group.ww"}, + log: syncuser_group, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const syncuser_passwd string = `backupFile: true +writeFile: true +Filename: etc/passwd +# Uncomment the following line to enable passwordless root login +# root::0:0:root:/root:/bin/bash +root:x:0:0:root:/root:/bin/bash +` + +const syncuser_group string = `backupFile: true +writeFile: true +Filename: etc/group +root:x:0: +` diff --git a/overlays/generic/rootfs/etc/group.ww b/overlays/syncuser/rootfs/etc/group.ww similarity index 100% rename from overlays/generic/rootfs/etc/group.ww rename to overlays/syncuser/rootfs/etc/group.ww diff --git a/overlays/generic/rootfs/etc/passwd.ww b/overlays/syncuser/rootfs/etc/passwd.ww similarity index 100% rename from overlays/generic/rootfs/etc/passwd.ww rename to overlays/syncuser/rootfs/etc/passwd.ww diff --git a/overlays/systemd.netname/internal/nodes.conf b/overlays/systemd.netname/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/systemd.netname/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/systemd.netname/internal/systemd_netname_test.go b/overlays/systemd.netname/internal/systemd_netname_test.go new file mode 100644 index 00000000..d70d530c --- /dev/null +++ b/overlays/systemd.netname/internal/systemd_netname_test.go @@ -0,0 +1,66 @@ +package systemd_netname + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_udev_netnameOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/systemd.netname/rootfs/etc/systemd/network/10-ww4-netname.link.ww", "../rootfs/etc/systemd/network/10-ww4-netname.link.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "systemd network links", + args: []string{"--render", "node1", "systemd.netname", "etc/systemd/network/10-ww4-netname.link.ww"}, + log: systemd_network_links, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const systemd_network_links string = `backupFile: true +writeFile: true +Filename: 10-ww4-netname-default.link +# This file is autogenerated by warewulf +[Match] +MACAddress=e6:92:39:49:7b:03 +[Link] +Name=wwnet0 +backupFile: true +writeFile: true +Filename: 10-ww4-netname-secondary.link +# This file is autogenerated by warewulf +[Match] +MACAddress=9a:77:29:73:14:f1 +[Link] +Name=wwnet1 +` diff --git a/overlays/wwinit/rootfs/etc/systemd/network/10-persistent-net.link.ww b/overlays/systemd.netname/rootfs/etc/systemd/network/10-ww4-netname.link.ww similarity index 62% rename from overlays/wwinit/rootfs/etc/systemd/network/10-persistent-net.link.ww rename to overlays/systemd.netname/rootfs/etc/systemd/network/10-ww4-netname.link.ww index fb84abd5..2047d56f 100644 --- a/overlays/wwinit/rootfs/etc/systemd/network/10-persistent-net.link.ww +++ b/overlays/systemd.netname/rootfs/etc/systemd/network/10-ww4-netname.link.ww @@ -1,10 +1,7 @@ {{ range $devname, $netdev := .NetDevs -}} -{{- $filename := print "10-persistent-net-" $devname ".link" }} +{{- $filename := print "10-ww4-netname-" $devname ".link" }} {{- file $filename }} # This file is autogenerated by warewulf -# Host: {{$.BuildHost}} -# Time: {{$.BuildTime}} -# Source: {{$.BuildSource}} {{- if and $netdev.Hwaddr $netdev.Device }} [Match] MACAddress={{$netdev.Hwaddr}} diff --git a/overlays/udev.netname/internal/nodes.conf b/overlays/udev.netname/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/udev.netname/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/udev.netname/internal/udev_netname_test.go b/overlays/udev.netname/internal/udev_netname_test.go new file mode 100644 index 00000000..55b9971c --- /dev/null +++ b/overlays/udev.netname/internal/udev_netname_test.go @@ -0,0 +1,58 @@ +package udev_netname + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_udev_netnameOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/udev.netname/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww", "../rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "udev.netname", + args: []string{"--render", "node1", "udev.netname", "etc/udev/rules.d/70-ww4-netname.rules.ww"}, + log: udev_netname, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const udev_netname string = `backupFile: true +writeFile: true +Filename: etc/udev/rules.d/70-ww4-netname.rules +# This file is autogenerated by warewulf + +SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="e6:92:39:49:7b:03", NAME="wwnet0" + +SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="9a:77:29:73:14:f1", NAME="wwnet1" +` diff --git a/overlays/wwinit/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww b/overlays/udev.netname/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww similarity index 71% rename from overlays/wwinit/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww rename to overlays/udev.netname/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww index f93048a0..30f71884 100644 --- a/overlays/wwinit/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww +++ b/overlays/udev.netname/rootfs/etc/udev/rules.d/70-ww4-netname.rules.ww @@ -1,9 +1,6 @@ # This file is autogenerated by warewulf -# Host: {{.BuildHost}} -# Time: {{.BuildTime}} -# Source: {{.BuildSource}} {{range $devname, $netdev := .NetDevs}} {{- if $netdev.Hwaddr }} SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="{{ $netdev.Hwaddr }}", NAME="{{ $netdev.Device }}" {{ end -}} -{{ end -}} \ No newline at end of file +{{ end -}} diff --git a/overlays/wicked/internal/nodes.conf b/overlays/wicked/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/wicked/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wicked/internal/wicked_test.go b/overlays/wicked/internal/wicked_test.go new file mode 100644 index 00000000..845edb3c --- /dev/null +++ b/overlays/wicked/internal/wicked_test.go @@ -0,0 +1,118 @@ +package wicked + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_wickedOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/wicked/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww", "../rootfs/etc/wicked/ifconfig/ifcfg.xml.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "wicked", + args: []string{"--render", "node1", "wicked", "etc/wicked/ifconfig/ifcfg.xml.ww"}, + log: wicked, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const wicked string = `backupFile: true +writeFile: true +Filename: ifcfg-default.xml + + + wwnet0 + ethernet + + boot + + + + + true + true + + +
+ 192.168.3.21/24 +
+ + + 192.168.3.1 + + +
+ + true + prefer-public + false + +
+backupFile: true +writeFile: true +Filename: ifcfg-secondary.xml + + + wwnet1 + ethernet + + boot + + + + + true + true + + +
+ 192.168.3.22/24 +
+ + + 192.168.3.1 + + +
+ + true + prefer-public + false + +
+` diff --git a/overlays/wwinit/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww b/overlays/wicked/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww similarity index 89% rename from overlays/wwinit/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww rename to overlays/wicked/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww index 8ce8b926..82573fdd 100644 --- a/overlays/wwinit/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww +++ b/overlays/wicked/rootfs/etc/wicked/ifconfig/ifcfg.xml.ww @@ -1,15 +1,9 @@ -{{- $host := .BuildHost }} -{{- $time := .BuildTime }} -{{- $source := .BuildSource }} {{ $NetDevs := .NetDevs -}}{{/* FIX: 1413 */ -}} {{range $devname, $netdev := .ThisNode.NetDevs -}} {{- $filename := print "ifcfg-" $devname ".xml" }} {{- file $filename }} {{$netdev.Device.Get}} diff --git a/overlays/wwclient/internal/nodes.conf b/overlays/wwclient/internal/nodes.conf new file mode 100644 index 00000000..bd740dee --- /dev/null +++ b/overlays/wwclient/internal/nodes.conf @@ -0,0 +1,58 @@ +WW_INTERNAL: 45 +nodeprofiles: + empty: {} +nodes: + node1: + profiles: + - empty + container name: rockylinux-9 + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:03 + ipaddr: 192.168.3.21 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + secondary: + device: wwnet1 + hwaddr: 9a:77:29:73:14:f1 + ipaddr: 192.168.3.22 + netmask: 255.255.255.0 + gateway: 192.168.3.1 + tags: + DNS1: 8.8.8.8 + DNS2: 8.8.4.4 + ipmi: + username: user + password: password + ipaddr: 192.168.4.21 + netmask: 255.255.255.0 + gateway: 192.168.4.1 + write: "true" + disks: + /dev/vda: + wipe_table: "true" + partitions: + scratch: + should_exist: "true" + swap: + number: "1" + size_mib: "1024" + filesystems: + /dev/disk/by-partlabel/scratch: + format: btrfs + path: /scratch + wipe_filesystem: "true" + /dev/disk/by-partlabel/swap: + format: swap + path: swap + node2: + profiles: + - empty + network devices: + default: + device: wwnet0 + hwaddr: e6:92:39:49:7b:04 + ipaddr: 192.168.3.23 + netmask: 255.255.255.0 + gateway: 192.168.3.1 diff --git a/overlays/wwclient/internal/wwclient_test.go b/overlays/wwclient/internal/wwclient_test.go new file mode 100644 index 00000000..d3591135 --- /dev/null +++ b/overlays/wwclient/internal/wwclient_test.go @@ -0,0 +1,61 @@ +package wwclient + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/warewulf/warewulf/internal/app/wwctl/overlay/show" + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_wwclientOverlay(t *testing.T) { + env := testenv.New(t) + defer env.RemoveAll(t) + env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf") + env.ImportFile(t, "var/lib/warewulf/overlays/wwclient/rootfs/warewulf/init.d/80-wwclient.ww", "../rootfs/warewulf/init.d/80-wwclient.ww") + + tests := []struct { + name string + args []string + log string + }{ + { + name: "wwclient:wwclient.ww", + args: []string{"--render", "node1", "wwclient", "warewulf/init.d/80-wwclient.ww"}, + log: wwinit_wwclient, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := show.GetCommand() + cmd.SetArgs(tt.args) + stdout := bytes.NewBufferString("") + stderr := bytes.NewBufferString("") + logbuf := bytes.NewBufferString("") + cmd.SetOut(stdout) + cmd.SetErr(stderr) + wwlog.SetLogWriter(logbuf) + err := cmd.Execute() + assert.NoError(t, err) + assert.Empty(t, stdout.String()) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.log, logbuf.String()) + }) + } +} + +const wwinit_wwclient string = `backupFile: true +writeFile: true +Filename: warewulf/init.d/80-wwclient +#!/bin/sh +# This file is autogenerated by warewulf + +. /warewulf/config +# Only start if the systemd is not available +test -e /usr/lib/systemd/systemd && exit 0 +echo "Starting wwclient" +nohup /warewulf/wwclient >/var/log/wwclient.log 2>&1 +debug /warewulf/template-variables.md.ww``. + +host +---- Configuration files used for the configuration of the Warewulf host / server are stored in the **host** overlay. Unlike other overlays, it @@ -91,25 +148,25 @@ The following services get configuration files via the host overlay: Combining Overlays ================== -When changing the overlays, it is recommended not to change them, but -to add the changed files to a new overlay and combine them in the +We recommended not changing the provided overlays. Rather, +add the changed files to a new overlay and combine them in the configuration. This is possible as the configuration fields for the -**wwinit** and **runtime** overlays are lists and can contain several +system and runtime overlays are lists and can contain several overlays. As an example for this, we will overwrite the -``/etc/issue`` file from the **wwinit** overlay. For this we will -create a new overlay called welcome and import the file ``/etc/issue`` +``/etc/issue`` file from the "issue" overlay. For this we will +create a new overlay called "welcome" and import the file ``/etc/issue`` from the host to it. This overlay is then combined with the existing -**wwinit** overlay. +overlays. .. code-block:: console # wwctl overlay create welcome # wwctl overlay mkdir welcome /etc # wwctl overlay import welcome /etc/issue - # wwctl profile set default --wwinit=wwinit,welcome + # wwctl profile set default --wwinit=wwinit,wwclient,welcome ? Are you sure you want to modify 1 profile(s)? [y/N] y # wwctl profile list default -a |grep welcome - default SystemOverlay wwinit,welcome + default SystemOverlay wwinit,wwclient,welcome Templates ========= @@ -143,7 +200,7 @@ permissions, etc. .. note:: - There is no possibility to delete files with an overlay! + It is not possible to delete files with an overlay. Build ----- diff --git a/userdocs/contents/provisioning.rst b/userdocs/contents/provisioning.rst index 58a708db..62e03a19 100644 --- a/userdocs/contents/provisioning.rst +++ b/userdocs/contents/provisioning.rst @@ -56,7 +56,7 @@ occur: #. The Warewulf server will generate the iPXE configuration which will include directions of what else is necessary to download and how to boot. - #. The kernel, container image, kernel modules, and system overlay + #. The kernel, container image, kernel modules, and overlays are all downloaded over REST HTTP from the Warewulf Server #. iPXE executes the kernel and processes the overlays to provide a unified root file system @@ -81,7 +81,7 @@ through the following command : # wwctl node status NODENAME STAGE SENT LASTSEEN (s) ================================================================================ - c001 RUNTIME_OVERLAY generic.img.gz 16 + c001 RUNTIME_OVERLAY __RUNTIME__.img.gz 16 For each node, there is 4 different stages : diff --git a/userdocs/contents/setup.rst b/userdocs/contents/setup.rst index 6f2773d7..83b379c6 100644 --- a/userdocs/contents/setup.rst +++ b/userdocs/contents/setup.rst @@ -118,8 +118,8 @@ You can check the result with ``wwctl node list``. deliverynet Comment default This profile is automatically included for each node deliverynet ContainerName default leap15.5 deliverynet Ipxe -- (default) - deliverynet RuntimeOverlay -- (generic) - deliverynet SystemOverlay -- (wwinit) + deliverynet RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser) + deliverynet SystemOverlay -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition) deliverynet Root -- (initramfs) deliverynet Init -- (/sbin/init) deliverynet Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) diff --git a/userdocs/contents/templating.rst b/userdocs/contents/templating.rst index 11543834..51b18753 100644 --- a/userdocs/contents/templating.rst +++ b/userdocs/contents/templating.rst @@ -8,7 +8,7 @@ on demand. In Warewulf, you can find templates both for the provisioning services (e.g. ``/etc/warewulf/ipxe/``, ``/etc/warewulf/dhcp/``, and -``/etc/warewulf/hosts.tmpl``) as well as within the runtime and system +``/etc/warewulf/hosts.tmpl``) as well as within many of the provided overlays. (more documentation coming soon) diff --git a/userdocs/contents/wwctl.rst b/userdocs/contents/wwctl.rst index dad05cb8..e62fd543 100644 --- a/userdocs/contents/wwctl.rst +++ b/userdocs/contents/wwctl.rst @@ -12,7 +12,7 @@ component of Warewulf's functionality. * ``node`` manages nodes in the cluster * ``profiles`` defines configuration which can be applied to multiple nodes -* ``overlays`` manages nodes +* ``overlays`` manages overlays * ``power`` turns nodes on and off * ``ssh`` provides basic parallel ssh functionality diff --git a/userdocs/contributing/development-environment-kvm.rst b/userdocs/contributing/development-environment-kvm.rst index c35ce668..3f981652 100644 --- a/userdocs/contributing/development-environment-kvm.rst +++ b/userdocs/contributing/development-environment-kvm.rst @@ -98,8 +98,8 @@ Build and install Warewulf on wwdev # Configure system service automatically sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service - sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay - sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay + sudo wwctl configure nfs # Configure NFS exports + sudo wwctl configure ssh # Build the basic ssh keys to be included by the ssh overlays # Pull and build the VNFS container and kernel sudo wwctl container import docker://ghcr.io/warewulf/warewulf-centos:7 centos-7 --setdefault diff --git a/userdocs/contributing/development-environment-vbox.rst b/userdocs/contributing/development-environment-vbox.rst index d960e407..17c9f16b 100644 --- a/userdocs/contributing/development-environment-vbox.rst +++ b/userdocs/contributing/development-environment-vbox.rst @@ -113,8 +113,8 @@ I have VirtualBox running on my desktop. # Configure system service automatically sudo wwctl configure dhcp --persist # Create the default dhcpd.conf file and start/enable service sudo wwctl configure tftp --persist # Install the base tftp/PXE boot files and start/enable service - sudo wwctl configure nfs --persist # Configure the exports and create an fstab in the default system overlay - sudo wwctl configure ssh --persist # Build the basic ssh keys to be included by the default system overlay + sudo wwctl configure nfs --persist # Configure the NFS exports + sudo wwctl configure ssh --persist # Build the basic ssh keys to be included by the SSH overlay # Pull and build the VNFS container and kernel sudo wwctl container import docker://ghcr.io/warewulf/warewulf-centos:7 centos-7 --setdefault diff --git a/userdocs/quickstart/debian12.rst b/userdocs/quickstart/debian12.rst index 89b359c6..30410130 100644 --- a/userdocs/quickstart/debian12.rst +++ b/userdocs/quickstart/debian12.rst @@ -201,7 +201,7 @@ typing the following: sudo wwctl node list -a n0000.cluster -To make node changes effective, it is a good practice to update warewulf +To make node changes effective, it is a good practice to update Warewulf overlays with the following command: .. code-block:: bash diff --git a/userdocs/quickstart/suse15.rst b/userdocs/quickstart/suse15.rst index bb15b7a8..a6039c7f 100644 --- a/userdocs/quickstart/suse15.rst +++ b/userdocs/quickstart/suse15.rst @@ -177,7 +177,7 @@ called. This enables us to prepopulate node configurations with content that is node specific like networking and service configurations. -Runtime overlays are provisioned after the node has booted and +Runtime overlays are re-applied periodically during the normal runtime of the node. Because these overlays are provisioned at periodic intervals, they are very useful for content that changes, like users and groups. diff --git a/warewulf.spec.in b/warewulf.spec.in index 2a6c43eb..36c3a113 100644 --- a/warewulf.spec.in +++ b/warewulf.spec.in @@ -191,10 +191,25 @@ getent group %{wwgroup} >/dev/null || groupadd -r %{wwgroup} %dir %{_sharedstatedir}/warewulf %dir %{_sharedstatedir}/warewulf/chroots %dir %{_sharedstatedir}/warewulf/overlays +%dir %{_sharedstatedir}/warewulf/overlays/NetworkManager +%dir %{_sharedstatedir}/warewulf/overlays/debian.interfaces %dir %{_sharedstatedir}/warewulf/overlays/debug -%dir %{_sharedstatedir}/warewulf/overlays/generic +%dir %{_sharedstatedir}/warewulf/overlays/fstab %dir %{_sharedstatedir}/warewulf/overlays/host +%dir %{_sharedstatedir}/warewulf/overlays/hostname +%dir %{_sharedstatedir}/warewulf/overlays/hosts +%dir %{_sharedstatedir}/warewulf/overlays/ifcfg +%dir %{_sharedstatedir}/warewulf/overlays/ignition +%dir %{_sharedstatedir}/warewulf/overlays/issue +%dir %{_sharedstatedir}/warewulf/overlays/resolv +%dir %{_sharedstatedir}/warewulf/overlays/ssh.authorized_keys +%dir %{_sharedstatedir}/warewulf/overlays/ssh.host_keys +%dir %{_sharedstatedir}/warewulf/overlays/syncuser +%dir %{_sharedstatedir}/warewulf/overlays/systemd.netname +%dir %{_sharedstatedir}/warewulf/overlays/udev.netname +%dir %{_sharedstatedir}/warewulf/overlays/wicked %dir %{_sharedstatedir}/warewulf/overlays/wwinit +%dir %{_sharedstatedir}/warewulf/overlays/wwclient %attr(-, root, root) %{_sharedstatedir}/warewulf/overlays/*/rootfs %attr(-, root, root) %{_bindir}/wwctl @@ -220,6 +235,9 @@ getent group %{wwgroup} >/dev/null || groupadd -r %{wwgroup} %changelog +* Thu Sep 12 2024 Jonathon Anderson +- Split overlays into separate directories + * Tue Jun 18 2024 Josh Burks - Add curl, cpio, and dracut as requires for warewulf-dracut