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() env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf") env.ImportFile("var/lib/warewulf/overlays/host/rootfs/etc/dhcp/dhcpd.conf.ww", "../rootfs/etc/dhcp/dhcpd.conf.ww") env.ImportFile("var/lib/warewulf/overlays/host/rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww", "../rootfs/etc/dnsmasq.d/ww4-hosts.conf.ww") env.ImportFile("var/lib/warewulf/overlays/host/rootfs/etc/exports.ww", "../rootfs/etc/exports.ww") env.ImportFile("var/lib/warewulf/overlays/host/rootfs/etc/hosts.ww", "../rootfs/etc/hosts.ww") env.ImportFile("var/lib/warewulf/overlays/host/rootfs/etc/profile.d/ssh_setup.csh.ww", "../rootfs/etc/profile.d/ssh_setup.csh.ww") env.ImportFile("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:dnsmasq(static)", conf: "warewulf.conf-static", args: []string{"--render", "host", "host", "etc/dnsmasq.d/ww4-hosts.conf.ww"}, log: host_dnsmasq_static, header: "", }, { name: "host:/etc/exports", conf: "warewulf.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("etc/warewulf/warewulf.conf", tt.conf) assert.NoError(t, config.Get().Read(env.GetPath("etc/warewulf/warewulf.conf"), false)) } 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; range 192.168.0.100 192.168.0.199; next-server 192.168.0.1; } 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/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 # IPv6 routing announcements enable-ra # IPv6 matching (client-arch=61) dhcp-match=set:x86PC,option6:61,0007 # EFI x86-64 dhcp-match=set:aarch64,option6:61,0011 # EFI aarch64 dhcp-vendorclass=set:x86PC,enterprise:343,PXEClient:Arch:00007 # EFI x86-64 dhcp-vendorclass=set:aarch64,enterprise:343,PXEClient:Arch:00011 # EFI aarch64 # IPv6 bootfile-url (iPXE EFI binary) dhcp-option=tag:x86PC,option6:bootfile-url,"tftp://[2001:db8::1]/warewulf/ipxe-snponly-x86_64.efi" dhcp-option=tag:aarch64,option6:bootfile-url,"tftp://[2001:db8::1]/warewulf/snponly.efi" # IPv6 iPXE binary will get the following configuration file dhcp-option=tag:iPXE,option6:bootfile-url,"http://[2001:db8::1]:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}" # define the dhcp range dhcp-range=192.168.0.100,192.168.0.199,255.255.255.0,6h dhcp-range=2001:db8::,ra-stateless,64,6h ` const host_dnsmasq_static 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/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 # IPv6 routing announcements enable-ra # IPv6 matching (client-arch=61) dhcp-match=set:x86PC,option6:61,0007 # EFI x86-64 dhcp-match=set:aarch64,option6:61,0011 # EFI aarch64 dhcp-vendorclass=set:x86PC,enterprise:343,PXEClient:Arch:00007 # EFI x86-64 dhcp-vendorclass=set:aarch64,enterprise:343,PXEClient:Arch:00011 # EFI aarch64 # IPv6 bootfile-url (iPXE EFI binary) dhcp-option=tag:x86PC,option6:bootfile-url,"tftp://[2001:db8::1]/warewulf/ipxe-snponly-x86_64.efi" dhcp-option=tag:aarch64,option6:bootfile-url,"tftp://[2001:db8::1]/warewulf/snponly.efi" # IPv6 iPXE binary will get the following configuration file dhcp-option=tag:iPXE,option6:bootfile-url,"http://[2001:db8::1]:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}" # define the dhcp range dhcp-range=192.168.0.100,192.168.0.199,255.255.255.0,6h dhcp-range=2001:db8::100,2001:db8::1FF,6h # hosts dhcp-host=e6:92:39:49:7b:03,id:00:03:00:01:e6:92:39:49:7b:03,set:warewulf,192.168.3.21,[2001:db8::111],node1,infinite dhcp-host=9a:77:29:73:14:f1,id:00:03:00:01:9a:77:29:73:14:f1,set:warewulf,192.168.3.22,[2001:db8::112],node1,infinite dhcp-host=e6:92:39:49:7b:04,id:00:03:00:01:e6:92:39:49:7b:04,set:warewulf,192.168.3.23,[2001:db8::121],node2,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) 2001:db8::/64(rw,sync) /opt 192.168.0.0/255.255.255.0(ro,sync,no_root_squash) 2001:db8::/64(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 # Warewulf Ipv6 2001:db8::1 %HOSTNAME% warewulf # Entry for node1 2001:db8::111 node1 node1-default node1-wwnet0 2001:db8::112 node1-secondary node1-wwnet1 # Entry for node2 2001:db8::121 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 ed25519 -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 ed25519 -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 `