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 <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2024-07-15 16:27:49 -06:00
parent b150b6319e
commit 053dd9953a
109 changed files with 3581 additions and 232 deletions

View File

@@ -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/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 - 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 - Disable building containers by default when calling `wwctl container copy`. #1378
- Split wwinit and generic overlays into discrete functionality. #987
### Fixed ### Fixed

View File

@@ -100,6 +100,7 @@ install: build docs
install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR) install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR)
install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR) install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR)
install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR) 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)$(WWOVERLAYDIR)/host/rootfs/$(TFTPDIR)/warewulf/
install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples
install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe
@@ -127,10 +128,10 @@ install: build docs
(cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;)
chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/init chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/init
chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/wwinit chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/wwinit
chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/etc/ssh/ssh* chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/config.ww
chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/ssh.host_keys/rootfs/etc/ssh/ssh*
chmod 0644 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/etc/ssh/ssh*.pub.ww chmod 0644 $(DESTDIR)$(WWOVERLAYDIR)/ssh.host_keys/rootfs/etc/ssh/ssh*.pub.ww
chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/warewulf/config.ww chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/NetworkManager/rootfs/etc/NetworkManager/system-connections/ww4-managed.ww
chmod 0750 $(DESTDIR)$(WWOVERLAYDIR)/host/rootfs chmod 0750 $(DESTDIR)$(WWOVERLAYDIR)/host/rootfs
install -m 0755 wwctl $(DESTDIR)$(BINDIR) install -m 0755 wwctl $(DESTDIR)$(BINDIR)
install -m 0755 wwclient $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/wwclient install -m 0755 wwclient $(DESTDIR)$(WWOVERLAYDIR)/wwinit/rootfs/$(WWCLIENTDIR)/wwclient

View File

@@ -18,9 +18,24 @@ structure of a node within \fBnodes.pp\fP.
.EX .EX
defaultnode: defaultnode:
runtime overlay: runtime overlay:
- generic - hosts
- ssh.authorized_keys
- syncuser
system overlay: system overlay:
- wwinit - wwinit
- wwclient
- fstab
- hostname
- ssh.host_keys
- issue
- resolv
- udev.netname
- systemd.netname
- ifcfg
- NetworkManager
- debian.interfaces
- wicked
- ignition
kernel: kernel:
args: quiet crashkernel=no vga=791 net.naming-scheme=v238 args: quiet crashkernel=no vga=791 net.naming-scheme=v238
init: /sbin/init init: /sbin/init

View File

@@ -145,8 +145,8 @@ nodes:
n01 Id -- n01 n01 Id -- n01
n01 Comment default profilecomment n01 Comment default profilecomment
n01 Ipxe -- (default) n01 Ipxe -- (default)
n01 RuntimeOverlay -- (generic) n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser)
n01 SystemOverlay -- (wwinit) 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 Root -- (initramfs)
n01 Init -- (/sbin/init) n01 Init -- (/sbin/init)
n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238)
@@ -168,8 +168,8 @@ nodes:
n01 Id -- n01 n01 Id -- n01
n01 Comment SUPERSEDED nodecomment n01 Comment SUPERSEDED nodecomment
n01 Ipxe -- (default) n01 Ipxe -- (default)
n01 RuntimeOverlay -- (generic) n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser)
n01 SystemOverlay -- (wwinit) 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 Root -- (initramfs)
n01 Init -- (/sbin/init) n01 Init -- (/sbin/init)
n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238)
@@ -251,8 +251,8 @@ nodes:
stdout: `NODE FIELD PROFILE VALUE stdout: `NODE FIELD PROFILE VALUE
n01 Id -- n01 n01 Id -- n01
n01 Ipxe -- (default) n01 Ipxe -- (default)
n01 RuntimeOverlay -- (generic) n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser)
n01 SystemOverlay -- (wwinit) 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 Root -- (initramfs)
n01 Init -- (/sbin/init) n01 Init -- (/sbin/init)
n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238)
@@ -273,12 +273,14 @@ nodes:
name: "multiple overlays list", name: "multiple overlays list",
args: []string{"-l"}, args: []string{"-l"},
stdout: `NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) 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 inDb: `WW_INTERNAL: 45
nodeprofiles: nodeprofiles:
p1: p1:
system overlay:
- sop1
runtime overlay: runtime overlay:
- rop1 - rop1
- rop2 - rop2
@@ -293,12 +295,14 @@ nodes:
name: "multiple overlays list", name: "multiple overlays list",
args: []string{"-l"}, args: []string{"-l"},
stdout: `NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) stdout: `NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R)
n01 -- -- (wwinit)/rop2,nop1 ~{rop1} n01 -- -- sop1/rop2,nop1 ~{rop1}
`}, `},
{ {
inDb: `WW_INTERNAL: 45 inDb: `WW_INTERNAL: 45
nodeprofiles: nodeprofiles:
p1: p1:
system overlay:
- sop1
runtime overlay: runtime overlay:
- rop1 - rop1
- rop2 - rop2
@@ -316,7 +320,7 @@ nodes:
n01 Id -- n01 n01 Id -- n01
n01 Ipxe -- (default) n01 Ipxe -- (default)
n01 RuntimeOverlay SUPERSEDED rop2,nop1 ~{rop1} n01 RuntimeOverlay SUPERSEDED rop2,nop1 ~{rop1}
n01 SystemOverlay -- (wwinit) n01 SystemOverlay p1 sop1
n01 Root -- (initramfs) n01 Root -- (initramfs)
n01 Init -- (/sbin/init) n01 Init -- (/sbin/init)
n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238) n01 Kernel.Args -- (quiet crashkernel=no vga=791 net.naming-scheme=v238)
@@ -340,7 +344,7 @@ nodes:
stdout: `NODE FIELD PROFILE VALUE stdout: `NODE FIELD PROFILE VALUE
n01 Id -- n01 n01 Id -- n01
n01 Ipxe -- (default) n01 Ipxe -- (default)
n01 RuntimeOverlay -- (generic) n01 RuntimeOverlay -- (hosts,ssh.authorized_keys,syncuser)
n01 SystemOverlay SUPERSEDED profileinit,nodeinit n01 SystemOverlay SUPERSEDED profileinit,nodeinit
n01 Root -- (initramfs) n01 Root -- (initramfs)
n01 Init -- (/sbin/init) n01 Init -- (/sbin/init)

View File

@@ -3,6 +3,7 @@ package show
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"errors"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@@ -24,30 +25,33 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
overlaySourceDir = overlay.OverlaySourceDir(overlayName) overlaySourceDir = overlay.OverlaySourceDir(overlayName)
if !util.IsDir(overlaySourceDir) { if !util.IsDir(overlaySourceDir) {
wwlog.Error("Overlay does not exist: %s", overlayName) err := errors.New("Overlay does not exist")
os.Exit(1) wwlog.Error("%s: %s", err, overlayName)
return err
} }
overlayFile := path.Join(overlaySourceDir, fileName) overlayFile := path.Join(overlaySourceDir, fileName)
if !util.IsFile(overlayFile) { if !util.IsFile(overlayFile) {
wwlog.Error("File does not exist within overlay: %s:%s", overlayName, fileName) err := errors.New("File does not exist within overlay")
os.Exit(1) wwlog.Error("%s: %s:%s", err, overlayName, fileName)
return err
} }
if NodeName == "" { if NodeName == "" {
f, err := os.ReadFile(overlayFile) f, err := os.ReadFile(overlayFile)
if err != nil { if err != nil {
wwlog.Error("Could not read file: %s", err) wwlog.Error("Could not read file: %s", err)
os.Exit(1) return err
} }
wwlog.Info(string(f)) wwlog.Output(string(f))
} else { } else {
if !util.IsFile(overlayFile) { if !util.IsFile(overlayFile) {
wwlog.Debug("%s is not a file", overlayFile) wwlog.Debug("%s is not a file", overlayFile)
wwlog.Error("%s:%s is not a file", overlayName, fileName) err := errors.New("Not a file")
os.Exit(1) wwlog.Error("%s: %s:%s", err, overlayName, fileName)
return err
} }
if filepath.Ext(overlayFile) != ".ww" { if filepath.Ext(overlayFile) != ".ww" {
wwlog.Warn("%s lacks the '.ww' suffix, will not be rendered in an overlay", fileName) 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() nodeDB, err := node.New()
if err != nil { if err != nil {
wwlog.Error("Could not open node configuration: %s", err) wwlog.Error("Could not open node configuration: %s", err)
os.Exit(1) return err
} }
nodes, err := nodeDB.FindAllNodes() nodes, err := nodeDB.FindAllNodes()
if err != nil { if err != nil {
wwlog.Error("Could not get node list: %s", err) wwlog.Error("Could not get node list: %s", err)
os.Exit(1) return err
} }
filteredNodes := node.FilterByName(nodes, []string{NodeName}) filteredNodes := node.FilterByName(nodes, []string{NodeName})
if hostName, err := os.Hostname(); err != nil { if hostName, err := os.Hostname(); err != nil {
@@ -73,8 +77,9 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
hostNodeInfo.ClusterName.Set(hostName) hostNodeInfo.ClusterName.Set(hostName)
filteredNodes = append(filteredNodes, *hostNodeInfo) filteredNodes = append(filteredNodes, *hostNodeInfo)
} else if len(filteredNodes) != 1 { } else if len(filteredNodes) != 1 {
wwlog.Error("%v does not identify a single node", NodeName) err := errors.New("Not a single node")
os.Exit(1) wwlog.Error("%s: %v", err, NodeName)
return err
} }
tstruct := overlay.InitStruct(&filteredNodes[0]) 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]) wwlog.Debug("Found multifile comment, new filename %s", filenameFromTemplate[0][1])
if foundFileComment { if foundFileComment {
if !Quiet { if !Quiet {
wwlog.Info("backupFile: %v\nwriteFile: %v", backupFile, writeFile) wwlog.Info("backupFile: %v", backupFile)
wwlog.Info("Filename: %s\n", destFileName) wwlog.Info("writeFile: %v", writeFile)
wwlog.Info("Filename: %s", destFileName)
} }
wwlog.Info("%s", outBuffer.String()) wwlog.Output(outBuffer.String())
outBuffer.Reset() outBuffer.Reset()
} }
destFileName = filenameFromTemplate[0][1] destFileName = filenameFromTemplate[0][1]
@@ -110,10 +116,11 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
} }
} }
if !Quiet { if !Quiet {
wwlog.Info("backupFile: %v\nwriteFile: %v", backupFile, writeFile) wwlog.Info("backupFile: %v", backupFile)
wwlog.Info("Filename: %s\n", destFileName) wwlog.Info("writeFile: %v", writeFile)
wwlog.Info("Filename: %s", destFileName)
} }
wwlog.Info("%s", outBuffer.String()) wwlog.Output("%s", outBuffer.String())
} }
return nil return nil
} }

View File

@@ -7,10 +7,9 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "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/testenv"
"github.com/warewulf/warewulf/internal/pkg/warewulfd" "github.com/warewulf/warewulf/internal/pkg/warewulfd"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
) )
var ( var (
@@ -24,6 +23,35 @@ overlay name {{ .Overlay }}
func Test_Overlay_List(t *testing.T) { func Test_Overlay_List(t *testing.T) {
env := testenv.New(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", env.WriteFile(t, "etc/warewulf/nodes.conf",
`WW_INTERNAL: 45 `WW_INTERNAL: 45
nodeprofiles: nodeprofiles:

View File

@@ -23,9 +23,24 @@ var (
var FallBackConf = `--- var FallBackConf = `---
defaultnode: defaultnode:
runtime overlay: runtime overlay:
- generic - hosts
- ssh.authorized_keys
- syncuser
system overlay: system overlay:
- wwinit - wwinit
- wwclient
- fstab
- hostname
- ssh.host_keys
- issue
- resolv
- udev.netname
- systemd.netname
- ifcfg
- NetworkManager
- debian.interfaces
- wicked
- ignition
kernel: kernel:
args: quiet crashkernel=no vga=791 net.naming-scheme=v238 args: quiet crashkernel=no vga=791 net.naming-scheme=v238
init: /sbin/init init: /sbin/init

View File

@@ -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 { func BuildAllOverlays(nodes []node.NodeInfo) error {
for _, n := range nodes { for _, n := range nodes {

View File

@@ -41,7 +41,6 @@ const Systemddir = "usr/lib/systemd/system"
const WWOverlaydir = "var/lib/warewulf/overlays" const WWOverlaydir = "var/lib/warewulf/overlays"
const WWChrootdir = "var/lib/warewulf/chroots" const WWChrootdir = "var/lib/warewulf/chroots"
const WWProvisiondir = "srv/warewulf" const WWProvisiondir = "srv/warewulf"
const WWClientdir = "warewulf"
// New creates a test environment in a temporary directory and configures // New creates a test environment in a temporary directory and configures
// Warewulf to use it. // Warewulf to use it.
@@ -78,7 +77,7 @@ func New(t *testing.T) (env *TestEnv) {
conf.Paths.WWOverlaydir = env.GetPath(WWOverlaydir) conf.Paths.WWOverlaydir = env.GetPath(WWOverlaydir)
conf.Paths.WWChrootdir = env.GetPath(WWChrootdir) conf.Paths.WWChrootdir = env.GetPath(WWChrootdir)
conf.Paths.WWProvisiondir = env.GetPath(WWProvisiondir) conf.Paths.WWProvisiondir = env.GetPath(WWProvisiondir)
conf.Paths.WWClientdir = env.GetPath(WWClientdir) conf.Paths.WWClientdir = "/warewulf"
for _, confPath := range []string{ for _, confPath := range []string{
conf.Paths.Sysconfdir, conf.Paths.Sysconfdir,
@@ -92,7 +91,6 @@ func New(t *testing.T) (env *TestEnv) {
conf.Paths.WWOverlaydir, conf.Paths.WWOverlaydir,
conf.Paths.WWChrootdir, conf.Paths.WWChrootdir,
conf.Paths.WWProvisiondir, conf.Paths.WWProvisiondir,
conf.Paths.WWClientdir,
} { } {
env.MkdirAll(t, confPath) env.MkdirAll(t, confPath)
} }
@@ -133,6 +131,15 @@ func (env *TestEnv) WriteFile(t *testing.T, fileName string, content string) {
assert.NoError(t, err) 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 // ReadFile returns the content of fileName as converted to a
// string. // string.
// //

View File

@@ -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 // NOTE: this is a bit strange, but for user-friendliness it makes sense
// to not pollute the messages unless something bad happens (by default). // to not pollute the messages unless something bad happens (by default).
// if logLevel > INFO, then level == INFO messages should not be printed anyway, // if logLevel > INFO, then level == INFO messages should not be printed anyway,

View File

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

View File

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

View File

@@ -1,12 +1,6 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
{{- $filename := print "warewulf-unmanaged.conf" }} {{- $filename := print "warewulf-unmanaged.conf" }}
{{- file $filename }} {{- file $filename }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{ $host }}
# Time: {{ $time }}
# Source: {{ $source }}
[main] [main]
plugins=keyfile plugins=keyfile

View File

@@ -1,13 +1,7 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
{{- range $connection_id, $netdev := .NetDevs }} {{- range $connection_id, $netdev := .NetDevs }}
{{- $filename := print "warewulf-" $connection_id ".conf" }} {{- $filename := print "warewulf-" $connection_id ".conf" }}
{{- file $filename }} {{- file $filename }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{ $host }}
# Time: {{ $time }}
# Source: {{ $source }}
[connection] [connection]
id={{ $connection_id }} id={{ $connection_id }}
interface-name={{ $netdev.Device }} interface-name={{ $netdev.Device }}

View File

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

View File

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

View File

@@ -1,13 +1,7 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
{{range $devname, $netdev := .ThisNode.NetDevs -}} {{range $devname, $netdev := .ThisNode.NetDevs -}}
{{- $filename := print $devname }} {{- $filename := print $devname }}
{{- file $filename }} {{- file $filename }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{ $host }}
# Time: {{ $time }}
# Source: {{ $source }}
{{ if $netdev.OnBoot.GetB -}} {{ if $netdev.OnBoot.GetB -}}
auto {{$netdev.Device.Get}} auto {{$netdev.Device.Get}}
{{ end -}} {{ end -}}

View File

@@ -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: <nil>
- 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:
`

View File

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

View File

@@ -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 available to Warewulf templates. It may also be rendered against a
node to debug its apparent configuration. 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 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. data from other structures.

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,4 @@
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
rootfs / tmpfs defaults 0 0 rootfs / tmpfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0
tmpfs /run/shm tmpfs defaults 0 0 tmpfs /run/shm tmpfs defaults 0 0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,5 @@
{{if $.Dhcp.Enabled -}} {{if $.Dhcp.Enabled -}}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
allow booting; allow booting;
allow bootp; allow bootp;

View File

@@ -1,7 +1,4 @@
# This file was autgenerated by warewulf # This file was autgenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source {{.BuildSource}}
{{ nobackup }} {{ nobackup }}
# select the x86 hosts which will get the iXPE binary # select the x86 hosts which will get the iXPE binary
dhcp-match=set:bios,option:client-arch,0 #legacy boot dhcp-match=set:bios,option:client-arch,0 #legacy boot

View File

@@ -1,8 +1,5 @@
{{- if .Nfs.Enabled }} {{- if .Nfs.Enabled }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
{{- $network := .Network }} {{- $network := .Network }}
{{- $netmask := .Netmask }} {{- $netmask := .Netmask }}
{{- range .Nfs.ExportsExtended }} {{- range .Nfs.ExportsExtended }}

View File

@@ -1,17 +1,14 @@
{{ IncludeBlock "/etc/hosts" "# Do not edit after this line" }} {{ IncludeBlock "/etc/hosts" "# Do not edit after this line" }}
# This block is autogenerated by warewulf # This block is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
# Warewulf Server # Warewulf Server
{{$.Ipaddr}} {{$.BuildHost}} warewulf {{$.Ipaddr}} {{$.BuildHost}} warewulf
{{- range $node := $.AllNodes}} {{/* for each node */}} {{- range $node := $.AllNodes }}{{/* for each node */}}
# Entry for {{$node.Id.Get}} # Entry for {{ $node.Id.Get }}
{{- range $devname, $netdev := $node.NetDevs}} {{/* for each network device on the node */}} {{- 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 */}} {{- 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 */}} {{- /* 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}} {{$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 }}{{/* if ip */}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,7 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
{{range $devname, $netdev := .NetDevs -}} {{range $devname, $netdev := .NetDevs -}}
{{- $filename := print "ifcfg-" $devname ".conf" }} {{- $filename := print "ifcfg-" $devname ".conf" }}
{{- file $filename }} {{- file $filename }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{ $host }}
# Time: {{ $time }}
# Source: {{ $source }}
TYPE={{ $netdev.Type }} TYPE={{ $netdev.Type }}
DEVICE={{ $netdev.Device }} DEVICE={{ $netdev.Device }}
NAME={{ $devname }} NAME={{ $devname }}

View File

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

View File

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

View File

@@ -1,7 +1,4 @@
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
[Unit] [Unit]
Description=mount ww4 disks Description=mount ww4 disks
# make sure that the disks are available # make sure that the disks are available

View File

@@ -1,6 +1,3 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
{{- range $fsdevice,$fs := .FileSystems }} {{- range $fsdevice,$fs := .FileSystems }}
{{- $prefix := replace "-" "\\x2d" $fs.Path }} {{- $prefix := replace "-" "\\x2d" $fs.Path }}
{{- $prefix = replace "/" "-" $prefix }} {{- $prefix = replace "/" "-" $prefix }}
@@ -11,9 +8,6 @@
{{- $filename := print $prefix "." $suffix }} {{- $filename := print $prefix "." $suffix }}
{{- file $filename }} {{- file $filename }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{ $host }}
# Time: {{ $time }}
# Source: {{ $source }}
{{- if eq $fs.Format "swap"}} {{- if eq $fs.Format "swap"}}
[Unit] [Unit]
ConditionPathExists=/warewulf/ignition.json ConditionPathExists=/warewulf/ignition.json

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,4 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{ $host }}
# Time: {{ $time }}
# Source: {{ $source }}
{{range $devname, $netdev := .NetDevs -}} {{range $devname, $netdev := .NetDevs -}}
{{range $tk, $tv := $netdev.Tags -}} {{range $tk, $tv := $netdev.Tags -}}
{{ $prefix := substr 0 3 $tk -}} {{ $prefix := substr 0 3 $tk -}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,7 @@
{{ range $devname, $netdev := .NetDevs -}} {{ range $devname, $netdev := .NetDevs -}}
{{- $filename := print "10-persistent-net-" $devname ".link" }} {{- $filename := print "10-ww4-netname-" $devname ".link" }}
{{- file $filename }} {{- file $filename }}
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{$.BuildHost}}
# Time: {{$.BuildTime}}
# Source: {{$.BuildSource}}
{{- if and $netdev.Hwaddr $netdev.Device }} {{- if and $netdev.Hwaddr $netdev.Device }}
[Match] [Match]
MACAddress={{$netdev.Hwaddr}} MACAddress={{$netdev.Hwaddr}}

View File

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

View File

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

View File

@@ -1,7 +1,4 @@
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
{{range $devname, $netdev := .NetDevs}} {{range $devname, $netdev := .NetDevs}}
{{- if $netdev.Hwaddr }} {{- if $netdev.Hwaddr }}
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="{{ $netdev.Hwaddr }}", NAME="{{ $netdev.Device }}" SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="{{ $netdev.Hwaddr }}", NAME="{{ $netdev.Device }}"

View File

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

View File

@@ -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
<!--
This file is autogenerated by warewulf
-->
<interface origin="static generated warewulf config">
<name>wwnet0</name>
<link-type>ethernet</link-type>
<control>
<mode>boot</mode>
</control>
<firewall/>
<link/>
<ipv4>
<enabled>true</enabled>
<arp-verify>true</arp-verify>
</ipv4>
<ipv4:static>
<address>
<local>192.168.3.21/24</local>
</address>
<route>
<nexthop>
<gateway>192.168.3.1</gateway>
</nexthop>
</route>
</ipv4:static>
<ipv6>
<enabled>true</enabled>
<privacy>prefer-public</privacy>
<accept-redirects>false</accept-redirects>
</ipv6>
</interface>
backupFile: true
writeFile: true
Filename: ifcfg-secondary.xml
<!--
This file is autogenerated by warewulf
-->
<interface origin="static generated warewulf config">
<name>wwnet1</name>
<link-type>ethernet</link-type>
<control>
<mode>boot</mode>
</control>
<firewall/>
<link/>
<ipv4>
<enabled>true</enabled>
<arp-verify>true</arp-verify>
</ipv4>
<ipv4:static>
<address>
<local>192.168.3.22/24</local>
</address>
<route>
<nexthop>
<gateway>192.168.3.1</gateway>
</nexthop>
</route>
</ipv4:static>
<ipv6>
<enabled>true</enabled>
<privacy>prefer-public</privacy>
<accept-redirects>false</accept-redirects>
</ipv6>
</interface>
`

View File

@@ -1,15 +1,9 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
{{ $NetDevs := .NetDevs -}}{{/* FIX: 1413 */ -}} {{ $NetDevs := .NetDevs -}}{{/* FIX: 1413 */ -}}
{{range $devname, $netdev := .ThisNode.NetDevs -}} {{range $devname, $netdev := .ThisNode.NetDevs -}}
{{- $filename := print "ifcfg-" $devname ".xml" }} {{- $filename := print "ifcfg-" $devname ".xml" }}
{{- file $filename }} {{- file $filename }}
<!-- <!--
This file is autogenerated by warewulf This file is autogenerated by warewulf
Host: {{ $host }}
Time: {{ $time }}
Source: {{ $source }}
--> -->
<interface origin="static generated warewulf config"> <interface origin="static generated warewulf config">
<name>{{$netdev.Device.Get}}</name> <name>{{$netdev.Device.Get}}</name>

View File

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

View File

@@ -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 </dev/null &
`

View File

@@ -1,8 +1,5 @@
#!/bin/sh #!/bin/sh
# This file is autogenerated by warewulf # This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
. /warewulf/config . /warewulf/config
# Only start if the systemd is not available # Only start if the systemd is not available

View File

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

View File

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

View File

@@ -0,0 +1,103 @@
package wwinit
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_wwinitOverlay(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll(t)
env.ImportFile(t, "etc/warewulf/warewulf.conf", "warewulf.conf")
env.ImportFile(t, "etc/warewulf/nodes.conf", "nodes.conf")
env.ImportFile(t, "var/lib/warewulf/overlays/wwinit/rootfs/etc/warewulf/warewulf.conf.ww", "../rootfs/etc/warewulf/warewulf.conf.ww")
env.ImportFile(t, "var/lib/warewulf/overlays/wwinit/rootfs/warewulf/config.ww", "../rootfs/warewulf/config.ww")
tests := []struct {
name string
args []string
log string
}{
{
name: "wwinit:warewulf.conf.ww",
args: []string{"--render", "node1", "wwinit", "etc/warewulf/warewulf.conf.ww"},
log: wwinit_warewulf_conf,
},
{
name: "wwinit:config.ww",
args: []string{"--render", "node1", "wwinit", "warewulf/config.ww"},
log: wwinit_config,
},
}
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_warewulf_conf string = `backupFile: true
writeFile: true
Filename: 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
`
const wwinit_config string = `backupFile: true
writeFile: true
Filename: warewulf/config
WWCONTAINER=rockylinux-9
WWHOSTNAME=node1
WWROOT=initramfs
WWINIT=/sbin/init
WWIPMI_IPADDR="192.168.4.21"
WWIPMI_NETMASK="255.255.255.0"
WWIPMI_GATEWAY="192.168.4.1"
WWIPMI_USER="user"
WWIPMI_PASSWORD="password"
WWIPMI_WRITE="true"
`

View File

@@ -98,7 +98,7 @@ explained as follows:
When ``true``, ``wwclient`` uses TCP port 987. When ``true``, ``wwclient`` uses TCP port 987.
Changing this option requires rebuilding node overlays and rebooting Changing this option requires rebuilding node overlays and rebooting
compute nodes, to configure them to use a privileged port. compute nodes to configure them to use a privileged port.
* ``warewulf:update interval``: This defines the frequency (in * ``warewulf:update interval``: This defines the frequency (in
seconds) with which the Warewulf client on the compute node fetches seconds) with which the Warewulf client on the compute node fetches
@@ -205,29 +205,7 @@ a "runtime overlay" specified, the respective value from
``defaultnode`` is used. If a network device does not specify a ``defaultnode`` is used. If a network device does not specify a
"device," the device value of the ``dummy`` device is used. "device," the device value of the ``dummy`` device is used.
If ``defaults.conf`` does not exist, the following values are used as If ``defaults.conf`` does not exist, compiled-in defaults are used.
compiled into Warewulf at build-time:
.. code-block:: yaml
--
defaultnode:
runtime overlay:
- generic
system overlay:
- wwinit
kernel:
args: quiet crashkernel=no vga=791 net.naming-scheme=v238
init: /sbin/init
root: initramfs
ipxe template: default
profiles:
- default
network devices:
dummy:
device: eth0
type: ethernet
netmask: 255.255.255.0
There should never be a need to change this file: all site-local There should never be a need to change this file: all site-local
parameters should be specified using either nodes or profiles. parameters should be specified using either nodes or profiles.

View File

@@ -57,7 +57,7 @@ Ignition Implementation
======================= =======================
The ignition implementation uses systemd services, as the underlying ``sgdisk`` command relies on dbus notifications. The ignition implementation uses systemd services, as the underlying ``sgdisk`` command relies on dbus notifications.
All necessary services are distributed by the ``wwinit`` overlay and depends on the existence of the file ``/warewulf/ignition.json``. All necessary services are distributed by the ``ignition`` overlay and depends on the existence of the file ``/warewulf/ignition.json``.
This file is created by the template function ``{{ createIgnitionJson }}`` only if the configuration contains necessary specifications for disks, partitions, and file systems. This file is created by the template function ``{{ createIgnitionJson }}`` only if the configuration contains necessary specifications for disks, partitions, and file systems.
If the file ``/warewulf/ignition.json`` exists, the service ``ignition-disks-ww4.service`` calls the ignition binary which takes creates partitions and file systems. If the file ``/warewulf/ignition.json`` exists, the service ``ignition-disks-ww4.service`` calls the ignition binary which takes creates partitions and file systems.
A systemd ``.mount`` unit is created for each configured file system, which also creates the necessary mount points in the root file system. A systemd ``.mount`` unit is created for each configured file system, which also creates the necessary mount points in the root file system.

View File

@@ -22,10 +22,10 @@ properly. Here are the things it will do:
the system service. the system service.
* **hostfile**: Update the system's /etc/hosts file based on the * **hostfile**: Update the system's /etc/hosts file based on the
**host** template ``/etc/hosts.ww``. **host** template ``/etc/hosts.ww``.
* **nfs**: Configure the NFS server on the control node as well as the * **nfs**: Configure the NFS server on the control node based on the
``/etc/fstab`` in the system overlay based on the configuration in configuration in ``/etc/warewulf/warewulf.conf`` and enable the NFS
``/etc/warewulf/warewulf.conf`` and enable the system service. Also service. Also the file ``/etc/exports.ww`` from the **host** template is
the file ``/etc/exports.ww`` from the **host** template is installed. installed.
* **ssh**: Create the appropriate host keys (stored in * **ssh**: Create the appropriate host keys (stored in
``/etc/warewulf/keys/``) and user keys for passwordless ``ssh`` into ``/etc/warewulf/keys/``) and user keys for passwordless ``ssh`` into
the nodes. Addionally the shell profiles the nodes. Addionally the shell profiles

View File

@@ -15,7 +15,7 @@ individual node would be the IP address.
The settings are only written to the IPMI interface if ``--ipmiwrite`` The settings are only written to the IPMI interface if ``--ipmiwrite``
is set to `true`. The write process happens at every boot of the node is set to `true`. The write process happens at every boot of the node
through the script ``/warewulf/init.d/50-ipmi`` in the **system** through the script ``/warewulf/init.d/50-ipmi`` in the wwinit
overlay. overlay.
If an individual node has different settings, you can set the IPMI If an individual node has different settings, you can set the IPMI
@@ -99,8 +99,8 @@ Node View
n001 cluster -- -- n001 cluster -- --
n001 container default sle-micro-5.3 n001 container default sle-micro-5.3
n001 ipxe -- (default) n001 ipxe -- (default)
n001 runtime -- (generic) n001 runtime -- (hosts,ssh.authorized_keys,syncuser)
n001 wwinit -- (wwinit) n001 wwinit -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition)
n001 root -- (initramfs) n001 root -- (initramfs)
n001 discoverable -- -- n001 discoverable -- --
n001 init -- (/sbin/init) n001 init -- (/sbin/init)

View File

@@ -87,8 +87,8 @@ You can also see the node's full attribute list by specifying the
n001 cluster -- -- n001 cluster -- --
n001 container default sle-micro-5.3 n001 container default sle-micro-5.3
n001 ipxe -- (default) n001 ipxe -- (default)
n001 runtime -- (generic) n001 runtime -- (hosts,ssh.authorized_keys,syncuser)
n001 wwinit -- (wwinit) n001 wwinit -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition)
n001 root -- (initramfs) n001 root -- (initramfs)
n001 discoverable -- -- n001 discoverable -- --
n001 init -- (/sbin/init) n001 init -- (/sbin/init)
@@ -193,8 +193,8 @@ container, kernel, and network:
n001 cluster -- -- n001 cluster -- --
n001 container default sle-micro-5.3 n001 container default sle-micro-5.3
n001 ipxe -- (default) n001 ipxe -- (default)
n001 runtime -- (generic) n001 runtime -- (hosts,ssh.authorized_keys,syncuser)
n001 wwinit -- (wwinit) n001 wwinit -- (wwinit,wwclient,fstab,hostname,ssh.host_keys,issue,resolv,udev.netname,systemd.netname,ifcfg,NetworkManager,debian.interfaces,wicked,ignition)
n001 root -- (initramfs) n001 root -- (initramfs)
n001 discoverable -- -- n001 discoverable -- --
n001 init -- (/sbin/init) n001 init -- (/sbin/init)

View File

@@ -2,7 +2,7 @@
Warewulf Overlays Warewulf Overlays
================= =================
So at this point, we have discussed how Warewulf is designed to At this point we have discussed how Warewulf is designed to
scalably provision and manage thousands of cluster nodes by utilizing scalably provision and manage thousands of cluster nodes by utilizing
identical stateless boot images. And there-in lies a problem to identical stateless boot images. And there-in lies a problem to
solve. If these boot images are completely identical, then how do we solve. If these boot images are completely identical, then how do we
@@ -19,57 +19,114 @@ through the provisioning process. A node or profile can configure an
overlay in two different ways: overlay in two different ways:
* An overlay can be configured to run during boot as part of the * An overlay can be configured to run during boot as part of the
``wwinit`` process. These overlays are called **system overlay** or ``wwinit`` process. These overlays are called **system overlays**.
**wwinit overlays**.
* An overlay can be configured to run periodically while the system is * An overlay can be configured to run periodically while the system is
running. These overlays are called **runtime overlays** or **generic running. These overlays are called **runtime overlays**.
overlays**.
The default profile has both a **wwinit** and a **runtime** overlay The default profile has both a **wwinit** and a **runtime** overlay
configured. configured.
Overlays are compiled for each compute node individually. Overlays are compiled for each compute node individually.
Defined Overlays Provided overlays
================ =================
System or wwinit overlay These overlays are provided as part of Warewulf.
------------------------
This overlay contains all the necessary scripts to provision a wwinit
Warewulf node. It is available before the ``systemd`` or other init is ------
called and contains all configurations which are needed to bring up
the compute node. It is not updated during run time. Besides the
network configurations for
* wicked The **wwinit** overlay performs initial configuration of the Warewulf node.
* NetworkManager Its `wwinit` script runs before ``systemd`` or other init is called and
* EL legacy network scripts contains all configurations which are needed to boot.
it also contains udev rules, which will set the interface names In particular:
based on the hardware addresses configured for the node.
Before the ``systemd`` init is called, the overlay loops through the scripts in
``/wwinit/warwulf/init.d/*`` which will setup
* Ipmi - Configure the loopback interface
* wwclient - Configure the BMC based on the node's configuration
* selinux - Update PAM configuration to allow missing shadow entries
- Relabel the file system for SELinux
Runtime Overlay or generic Overlay Other overlays may place additional scripts in ``/warewulf/init.d/`` to affect
---------------------------------- node configuration in this pre-boot environment.
The runtime overlay is updated by the ``wwclient`` service on a wwclient
regular basis (by default, once per minute). In the standard --------
configuration it includes updates for ``/etc/passwd``, ``/etc/group``
and ``/etc/hosts``. Additionally the ``authorized_keys`` file of the
root user is updated. It is recommended to use this overlay for
dynamic configuration files like ``slurm.conf``. Once the system is
provisioned and booted, the ``wwclient`` program (which is provisioned
as part of the ``wwinit`` system overlay) will continuously update the
node with updates in the runtime overlay.
Host Overlay All configured overlays are provisioned initially along with the node image
------------ itself; but **wwclient** periodically fetches and applies the runtime overlay
to allow configuration of some settings without a reboot.
Network interfaces
------------------
Warewulf ships with support for many different network interface configuration
systems. All of these are applied by default; but the list may be trimmed to
the desired system.
- ifcfg
- NetworkManager
- debian.interfaces
- wicked
Warewulf also configures both systemd and udev with the intended names of
configured network interfaces, typically based on a known MAC address.
- systemd.netname
- udev.netname
Basics
------
The **hostname** overlay sets the hostname based on the configured Warewulf
node name.
The **hosts** overlay configures ``/etc/hosts`` to include all Warewulf nodes.
The **issue** overlay configures a standard Warewulf status message for display
during login.
The **resolv** overlay configures ``/etc/resolv.conf`` based on the value of
"DNS" nettags. (In most situations this should be unnecessary, as the network
interface configuration should handle this dynamically.)
fstab
-----
The **fstab** overlay configures ``/etc/fstab`` to mount NFS shares defined in
``/etc/warewulf.conf`` and file systems created by Ignition.
ssh
---
Two SSH overlays configure host keys (one set for all node in the cluster) and
``authorized_keys`` for the root account.
- ssh.authorized_keys
- ssh.host_keys
syncuser
--------
The **syncuser** overlay updates ``/etc/passwd`` and ``/etc/group`` to include
all users on both the Warewulf server and from the container image. To function
properly, ``wwctl container syncuser`` must have also been run on the container
image to synchronize its user and group IDs with those of the server.
ignition
--------
The **ignition** overlay defines partitions and file systems on local disks.
debug
-----
The **debug** overlay is not intended to be used in configuration, but can be
used as an example and applied with ``wwctl overlay show --render <nodename>
debug /warewulf/template-variables.md.ww``.
host
----
Configuration files used for the configuration of the Warewulf host / Configuration files used for the configuration of the Warewulf host /
server are stored in the **host** overlay. Unlike other overlays, it 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 Combining Overlays
================== ==================
When changing the overlays, it is recommended not to change them, but We recommended not changing the provided overlays. Rather,
to add the changed files to a new overlay and combine them in the add the changed files to a new overlay and combine them in the
configuration. This is possible as the configuration fields for 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 overlays. As an example for this, we will overwrite the
``/etc/issue`` file from the **wwinit** overlay. For this we will ``/etc/issue`` file from the "issue" overlay. For this we will
create a new overlay called welcome and import the file ``/etc/issue`` 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 from the host to it. This overlay is then combined with the existing
**wwinit** overlay. overlays.
.. code-block:: console .. code-block:: console
# wwctl overlay create welcome # wwctl overlay create welcome
# wwctl overlay mkdir welcome /etc # wwctl overlay mkdir welcome /etc
# wwctl overlay import welcome /etc/issue # 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 ? Are you sure you want to modify 1 profile(s)? [y/N] y
# wwctl profile list default -a |grep welcome # wwctl profile list default -a |grep welcome
default SystemOverlay wwinit,welcome default SystemOverlay wwinit,wwclient,welcome
Templates Templates
========= =========
@@ -143,7 +200,7 @@ permissions, etc.
.. ..
note:: note::
There is no possibility to delete files with an overlay! It is not possible to delete files with an overlay.
Build Build
----- -----

Some files were not shown because too many files have changed in this diff Show More