Fix handling of CIDR values in warewulf.conf
- Fixes: #1541 - Fixes: #1594 Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Add completion for profile list. #1695
|
- Add completion for profile list. #1695
|
||||||
- Add OPTIONS argument for `warewulfd.service`. #1707
|
- Add OPTIONS argument for `warewulfd.service`. #1707
|
||||||
- Document `warewulf.conf:dhcp.template`. #1701
|
- Document `warewulf.conf:dhcp.template`. #1701
|
||||||
|
- New template field `IpCIDR`. #1700
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Only calculate image chroot size when requested. #1504
|
- Only calculate image chroot size when requested. #1504
|
||||||
- Create temporary files in overlay directory during `wwctl overlay edit`. #1473
|
- Create temporary files in overlay directory during `wwctl overlay edit`. #1473
|
||||||
- Re-order SSH key types to make ed25519 default. #981
|
- Re-order SSH key types to make ed25519 default. #981
|
||||||
|
- Don't assume default values for `warewulf.conf` network settings. #1700
|
||||||
|
- Omit DHCP pool from `dhcpd.conf` if any required fields are missing. #1700
|
||||||
|
- `warewulf.conf:ipaddr6` is no longer required to be a `/64` or smaller. #1700
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -46,6 +50,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Remove `warewulf.conf:syslog`. #1606
|
- Remove `warewulf.conf:syslog`. #1606
|
||||||
|
- Properly handle parsing of server network and netmask from CIDR `warewulf.conf:ipaddr`. #1541, #1594
|
||||||
|
- Populate template field `NetworkCIDR`. #1700
|
||||||
|
|
||||||
## v4.6.0rc1, 2025-01-29
|
## v4.6.0rc1, 2025-01-29
|
||||||
|
|
||||||
|
|||||||
@@ -92,8 +92,5 @@ func rootPersistentPreRunE(cmd *cobra.Command, args []string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = conf.SetDynamicDefaults(); err != nil {
|
|
||||||
wwlog.Error("error setting default configuration: %s", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@@ -103,98 +100,55 @@ func (conf *WarewulfYaml) Parse(data []byte) error {
|
|||||||
conf.TFTP.IpxeBinaries = defIpxe
|
conf.TFTP.IpxeBinaries = defIpxe
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether ip addr is CIDR type and configure related fields as required
|
|
||||||
if ip, network, err := net.ParseCIDR(conf.Ipaddr); err == nil {
|
if ip, network, err := net.ParseCIDR(conf.Ipaddr); err == nil {
|
||||||
conf.Ipaddr = ip.String()
|
conf.Ipaddr = ip.String()
|
||||||
if conf.Network == "" {
|
if conf.Network == "" {
|
||||||
conf.Network = network.IP.String()
|
conf.Network = network.IP.String()
|
||||||
}
|
}
|
||||||
if conf.Netmask == "" {
|
if conf.Netmask == "" {
|
||||||
mask := network.Mask
|
conf.Netmask = net.IP(network.Mask).String()
|
||||||
conf.Netmask = fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.Ipaddr6 != "" {
|
||||||
|
if _, network, err := net.ParseCIDR(conf.Ipaddr6); err == nil {
|
||||||
|
if conf.Ipv6net == "" {
|
||||||
|
conf.Ipv6net = network.IP.String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid ipv6 address: must use CIDR notation: %s", conf.Ipaddr6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDynamicDefaults populates [WarewulfYaml] with plausible defaults for
|
func (config *WarewulfYaml) NetworkCIDR() string {
|
||||||
// the runtime environment.
|
if config.Network == "" || config.Netmask == "" {
|
||||||
func (conf *WarewulfYaml) SetDynamicDefaults() (err error) {
|
return ""
|
||||||
if conf.Ipaddr == "" || conf.Netmask == "" || conf.Network == "" {
|
|
||||||
var mask net.IPMask
|
|
||||||
var network *net.IPNet
|
|
||||||
var ip net.IP
|
|
||||||
cidr := conf.Ipaddr
|
|
||||||
|
|
||||||
if conf.Ipaddr == "" {
|
|
||||||
wwlog.Verbose("Configuration has no valid network, going to dynamic values")
|
|
||||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
|
||||||
if err == nil {
|
|
||||||
defer conn.Close()
|
|
||||||
ipaddr := conn.LocalAddr().(*net.UDPAddr).IP
|
|
||||||
mask = ipaddr.DefaultMask()
|
|
||||||
sz, _ := mask.Size()
|
|
||||||
cidr = ipaddr.String() + fmt.Sprintf("/%d", sz)
|
|
||||||
} else {
|
|
||||||
cidr = "192.168.1.1/24"
|
|
||||||
}
|
|
||||||
} else if addr, err := netip.ParseAddr(conf.Ipaddr); err == nil {
|
|
||||||
// if the ipaddr does not have mask appended, update it with default generated mask
|
|
||||||
ipaddr := net.IP(addr.AsSlice())
|
|
||||||
sz, _ := ipaddr.DefaultMask().Size()
|
|
||||||
cidr = fmt.Sprintf("%s/%d", conf.Ipaddr, sz)
|
|
||||||
// otherwise, the following code will handle the ipaddr format: invalid or xxx.xxx.xxx.xxx/xx
|
|
||||||
}
|
|
||||||
|
|
||||||
ip, network, err = net.ParseCIDR(cidr)
|
|
||||||
if err == nil {
|
|
||||||
mask = network.Mask
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Couldn't parse IP address: %w", err)
|
|
||||||
}
|
|
||||||
if conf.Netmask == "" {
|
|
||||||
conf.Netmask = fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3])
|
|
||||||
wwlog.Verbose("Netmask address is not configured in warewulf.conf, using %s", conf.Netmask)
|
|
||||||
}
|
|
||||||
if conf.Network == "" {
|
|
||||||
conf.Network = network.IP.String()
|
|
||||||
wwlog.Verbose("Network is not configured in warewulf.conf, using %s", conf.Network)
|
|
||||||
}
|
|
||||||
// always update the ipaddr to valid ip
|
|
||||||
conf.Ipaddr = ip.String()
|
|
||||||
}
|
}
|
||||||
if conf.DHCP.RangeStart == "" && conf.DHCP.RangeEnd == "" {
|
cidr := net.IPNet{
|
||||||
start := net.ParseIP(conf.Network).To4()
|
IP: net.ParseIP(config.Network),
|
||||||
start[3] += 1
|
Mask: net.IPMask(net.ParseIP(config.Netmask)),
|
||||||
if start.Equal(net.ParseIP(conf.Ipaddr)) {
|
}
|
||||||
start[3] += 1
|
if cidr.IP == nil || cidr.Mask == nil {
|
||||||
}
|
return ""
|
||||||
conf.DHCP.RangeStart = start.String()
|
}
|
||||||
wwlog.Verbose("dhpd start is not configured in warewulf.conf, using %s", conf.DHCP.RangeStart)
|
return cidr.String()
|
||||||
sz, _ := net.IPMask(net.ParseIP(conf.Netmask).To4()).Size()
|
}
|
||||||
range_end := (1 << (32 - sz)) / 8
|
|
||||||
if range_end > 127 {
|
|
||||||
range_end = 127
|
|
||||||
}
|
|
||||||
end := net.ParseIP(conf.Network).To4()
|
|
||||||
end[3] += byte(range_end)
|
|
||||||
conf.DHCP.RangeEnd = end.String()
|
|
||||||
wwlog.Verbose("dhpd end is not configured in warewulf.conf, using %s", conf.DHCP.RangeEnd)
|
|
||||||
|
|
||||||
|
func (config *WarewulfYaml) IpCIDR() string {
|
||||||
|
if config.Ipaddr == "" || config.Netmask == "" {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
// check validity of ipv6 net
|
cidr := net.IPNet{
|
||||||
if conf.Ipaddr6 != "" {
|
IP: net.ParseIP(config.Ipaddr),
|
||||||
_, ipv6net, err := net.ParseCIDR(conf.Ipaddr6)
|
Mask: net.IPMask(net.ParseIP(config.Netmask)),
|
||||||
if err != nil {
|
|
||||||
wwlog.Error("Invalid ipv6 address specified, mut be CIDR notation: %s", conf.Ipaddr6)
|
|
||||||
return errors.New("invalid ipv6 network")
|
|
||||||
}
|
|
||||||
if msize, _ := ipv6net.Mask.Size(); msize > 64 {
|
|
||||||
wwlog.Error("ipv6 mask size must be smaller than 64")
|
|
||||||
return errors.New("invalid ipv6 network size")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
if cidr.IP == nil || cidr.Mask == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return cidr.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializedFromFile returns true if [WarewulfYaml] memory was read from
|
// InitializedFromFile returns true if [WarewulfYaml] memory was read from
|
||||||
|
|||||||
@@ -7,69 +7,289 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultWarewulfYaml(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
conf := New()
|
tests := map[string]struct {
|
||||||
|
input string
|
||||||
assert.Equal(t, 9873, conf.Warewulf.Port)
|
result string
|
||||||
assert.True(t, conf.Warewulf.Secure())
|
}{
|
||||||
assert.Equal(t, 60, conf.Warewulf.UpdateInterval)
|
"default": {
|
||||||
assert.True(t, conf.Warewulf.AutobuildOverlays())
|
input: ``,
|
||||||
assert.True(t, conf.Warewulf.EnableHostOverlay())
|
result: `
|
||||||
|
warewulf:
|
||||||
assert.True(t, conf.DHCP.Enabled())
|
autobuild overlays: true
|
||||||
assert.Equal(t, "default", conf.DHCP.Template)
|
grubboot: false
|
||||||
assert.Empty(t, conf.DHCP.RangeStart)
|
host overlay: true
|
||||||
assert.Empty(t, conf.DHCP.RangeEnd)
|
port: 9873
|
||||||
assert.Equal(t, "dhcpd", conf.DHCP.SystemdName)
|
secure: true
|
||||||
|
update interval: 60
|
||||||
assert.True(t, conf.TFTP.Enabled())
|
nfs:
|
||||||
assert.NotEmpty(t, conf.TFTP.TftpRoot)
|
enabled: true
|
||||||
assert.Equal(t, "tftp", conf.TFTP.SystemdName)
|
systemd name: nfsd
|
||||||
assert.NotEmpty(t, conf.TFTP.IpxeBinaries["00:00"])
|
dhcp:
|
||||||
assert.NotEmpty(t, conf.TFTP.IpxeBinaries["00:07"])
|
enabled: true
|
||||||
assert.NotEmpty(t, conf.TFTP.IpxeBinaries["00:09"])
|
systemd name: dhcpd
|
||||||
assert.NotEmpty(t, conf.TFTP.IpxeBinaries["00:0B"])
|
template: default
|
||||||
|
image mounts:
|
||||||
assert.True(t, conf.NFS.Enabled())
|
- dest: /etc/resolv.conf
|
||||||
assert.Empty(t, conf.NFS.ExportsExtended)
|
source: /etc/resolv.conf
|
||||||
assert.Equal(t, "nfsd", conf.NFS.SystemdName)
|
paths:
|
||||||
|
bindir: /usr/local/bin
|
||||||
assert.Equal(t, "/etc/resolv.conf", conf.MountsImage[0].Source)
|
cachedir: /var/local/cache
|
||||||
assert.Equal(t, "/etc/resolv.conf", conf.MountsImage[0].Dest)
|
datadir: /usr/local/share
|
||||||
assert.False(t, conf.MountsImage[0].ReadOnly())
|
firewallddir: /usr/lib/firewalld/services
|
||||||
assert.Empty(t, conf.MountsImage[0].Options)
|
ipxesource: /usr/local/share/ipxe
|
||||||
|
localstatedir: /var/local
|
||||||
assert.NotEmpty(t, conf.Paths.Bindir)
|
srvdir: /srv
|
||||||
assert.NotEmpty(t, conf.Paths.Sysconfdir)
|
sysconfdir: /usr/local/etc
|
||||||
assert.NotEmpty(t, conf.Paths.Localstatedir)
|
systemddir: /usr/lib/systemd/system
|
||||||
assert.NotEmpty(t, conf.Paths.Srvdir)
|
wwchrootdir: /var/local/warewulf/chroots
|
||||||
assert.NotEmpty(t, conf.Paths.Firewallddir)
|
wwclientdir: /warewulf
|
||||||
assert.NotEmpty(t, conf.Paths.Systemddir)
|
wwoverlaydir: /var/local/warewulf/overlays
|
||||||
assert.NotEmpty(t, conf.Paths.WWOverlaydir)
|
wwprovisiondir: /var/local/warewulf/provision
|
||||||
assert.NotEmpty(t, conf.Paths.WWChrootdir)
|
ssh:
|
||||||
assert.NotEmpty(t, conf.Paths.WWProvisiondir)
|
key types:
|
||||||
assert.NotEmpty(t, conf.Paths.WWClientdir)
|
- ed25519
|
||||||
assert.NotEmpty(t, conf.Paths.Cachedir)
|
- ecdsa
|
||||||
}
|
- rsa
|
||||||
|
- dsa
|
||||||
func TestInitializedFromFile(t *testing.T) {
|
tftp:
|
||||||
example_warewulf_conf := ""
|
enabled: true
|
||||||
tempWarewulfConf, warewulfConfErr := os.CreateTemp("", "warewulf.conf-")
|
ipxe:
|
||||||
assert.NoError(t, warewulfConfErr)
|
"00:00": undionly.kpxe
|
||||||
defer os.Remove(tempWarewulfConf.Name())
|
"00:07": ipxe-snponly-x86_64.efi
|
||||||
_, warewulfConfErr = tempWarewulfConf.Write([]byte(example_warewulf_conf))
|
"00:09": ipxe-snponly-x86_64.efi
|
||||||
assert.NoError(t, warewulfConfErr)
|
"00:0B": arm64-efi/snponly.efi
|
||||||
assert.NoError(t, tempWarewulfConf.Sync())
|
systemd name: tftp
|
||||||
|
tftproot: /var/lib/tftpboot
|
||||||
conf := New()
|
`,
|
||||||
assert.False(t, conf.InitializedFromFile())
|
},
|
||||||
assert.NoError(t, conf.Read(tempWarewulfConf.Name()))
|
"cidr": {
|
||||||
assert.True(t, conf.InitializedFromFile())
|
input: `
|
||||||
assert.Equal(t, conf.GetWarewulfConf(), tempWarewulfConf.Name())
|
ipaddr: 192.168.0.1/24
|
||||||
}
|
`,
|
||||||
|
result: `
|
||||||
func TestExampleWarewulfYaml(t *testing.T) {
|
ipaddr: 192.168.0.1
|
||||||
example_warewulf_conf := `
|
network: 192.168.0.0
|
||||||
|
netmask: 255.255.255.0
|
||||||
|
warewulf:
|
||||||
|
autobuild overlays: true
|
||||||
|
grubboot: false
|
||||||
|
host overlay: true
|
||||||
|
port: 9873
|
||||||
|
secure: true
|
||||||
|
update interval: 60
|
||||||
|
nfs:
|
||||||
|
enabled: true
|
||||||
|
systemd name: nfsd
|
||||||
|
dhcp:
|
||||||
|
enabled: true
|
||||||
|
systemd name: dhcpd
|
||||||
|
template: default
|
||||||
|
image mounts:
|
||||||
|
- dest: /etc/resolv.conf
|
||||||
|
source: /etc/resolv.conf
|
||||||
|
paths:
|
||||||
|
bindir: /usr/local/bin
|
||||||
|
cachedir: /var/local/cache
|
||||||
|
datadir: /usr/local/share
|
||||||
|
firewallddir: /usr/lib/firewalld/services
|
||||||
|
ipxesource: /usr/local/share/ipxe
|
||||||
|
localstatedir: /var/local
|
||||||
|
srvdir: /srv
|
||||||
|
sysconfdir: /usr/local/etc
|
||||||
|
systemddir: /usr/lib/systemd/system
|
||||||
|
wwchrootdir: /var/local/warewulf/chroots
|
||||||
|
wwclientdir: /warewulf
|
||||||
|
wwoverlaydir: /var/local/warewulf/overlays
|
||||||
|
wwprovisiondir: /var/local/warewulf/provision
|
||||||
|
ssh:
|
||||||
|
key types:
|
||||||
|
- ed25519
|
||||||
|
- ecdsa
|
||||||
|
- rsa
|
||||||
|
- dsa
|
||||||
|
tftp:
|
||||||
|
enabled: true
|
||||||
|
ipxe:
|
||||||
|
"00:00": undionly.kpxe
|
||||||
|
"00:07": ipxe-snponly-x86_64.efi
|
||||||
|
"00:09": ipxe-snponly-x86_64.efi
|
||||||
|
"00:0B": arm64-efi/snponly.efi
|
||||||
|
systemd name: tftp
|
||||||
|
tftproot: /var/lib/tftpboot
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"cidr with conflicts": {
|
||||||
|
input: `
|
||||||
|
ipaddr: 192.168.1.1/24
|
||||||
|
network: 192.168.0.0
|
||||||
|
netmask: 255.255.0.0
|
||||||
|
`,
|
||||||
|
result: `
|
||||||
|
ipaddr: 192.168.1.1
|
||||||
|
network: 192.168.0.0
|
||||||
|
netmask: 255.255.0.0
|
||||||
|
warewulf:
|
||||||
|
autobuild overlays: true
|
||||||
|
grubboot: false
|
||||||
|
host overlay: true
|
||||||
|
port: 9873
|
||||||
|
secure: true
|
||||||
|
update interval: 60
|
||||||
|
nfs:
|
||||||
|
enabled: true
|
||||||
|
systemd name: nfsd
|
||||||
|
dhcp:
|
||||||
|
enabled: true
|
||||||
|
systemd name: dhcpd
|
||||||
|
template: default
|
||||||
|
image mounts:
|
||||||
|
- dest: /etc/resolv.conf
|
||||||
|
source: /etc/resolv.conf
|
||||||
|
paths:
|
||||||
|
bindir: /usr/local/bin
|
||||||
|
cachedir: /var/local/cache
|
||||||
|
datadir: /usr/local/share
|
||||||
|
firewallddir: /usr/lib/firewalld/services
|
||||||
|
ipxesource: /usr/local/share/ipxe
|
||||||
|
localstatedir: /var/local
|
||||||
|
srvdir: /srv
|
||||||
|
sysconfdir: /usr/local/etc
|
||||||
|
systemddir: /usr/lib/systemd/system
|
||||||
|
wwchrootdir: /var/local/warewulf/chroots
|
||||||
|
wwclientdir: /warewulf
|
||||||
|
wwoverlaydir: /var/local/warewulf/overlays
|
||||||
|
wwprovisiondir: /var/local/warewulf/provision
|
||||||
|
ssh:
|
||||||
|
key types:
|
||||||
|
- ed25519
|
||||||
|
- ecdsa
|
||||||
|
- rsa
|
||||||
|
- dsa
|
||||||
|
tftp:
|
||||||
|
enabled: true
|
||||||
|
ipxe:
|
||||||
|
"00:00": undionly.kpxe
|
||||||
|
"00:07": ipxe-snponly-x86_64.efi
|
||||||
|
"00:09": ipxe-snponly-x86_64.efi
|
||||||
|
"00:0B": arm64-efi/snponly.efi
|
||||||
|
systemd name: tftp
|
||||||
|
tftproot: /var/lib/tftpboot
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"ipv6 cidr": {
|
||||||
|
input: `
|
||||||
|
ipaddr6: "2001:db8::1/64"
|
||||||
|
`,
|
||||||
|
result: `
|
||||||
|
ipaddr6: "2001:db8::1/64"
|
||||||
|
ipv6net: "2001:db8::"
|
||||||
|
warewulf:
|
||||||
|
autobuild overlays: true
|
||||||
|
grubboot: false
|
||||||
|
host overlay: true
|
||||||
|
port: 9873
|
||||||
|
secure: true
|
||||||
|
update interval: 60
|
||||||
|
nfs:
|
||||||
|
enabled: true
|
||||||
|
systemd name: nfsd
|
||||||
|
dhcp:
|
||||||
|
enabled: true
|
||||||
|
systemd name: dhcpd
|
||||||
|
template: default
|
||||||
|
image mounts:
|
||||||
|
- dest: /etc/resolv.conf
|
||||||
|
source: /etc/resolv.conf
|
||||||
|
paths:
|
||||||
|
bindir: /usr/local/bin
|
||||||
|
cachedir: /var/local/cache
|
||||||
|
datadir: /usr/local/share
|
||||||
|
firewallddir: /usr/lib/firewalld/services
|
||||||
|
ipxesource: /usr/local/share/ipxe
|
||||||
|
localstatedir: /var/local
|
||||||
|
srvdir: /srv
|
||||||
|
sysconfdir: /usr/local/etc
|
||||||
|
systemddir: /usr/lib/systemd/system
|
||||||
|
wwchrootdir: /var/local/warewulf/chroots
|
||||||
|
wwclientdir: /warewulf
|
||||||
|
wwoverlaydir: /var/local/warewulf/overlays
|
||||||
|
wwprovisiondir: /var/local/warewulf/provision
|
||||||
|
ssh:
|
||||||
|
key types:
|
||||||
|
- ed25519
|
||||||
|
- ecdsa
|
||||||
|
- rsa
|
||||||
|
- dsa
|
||||||
|
tftp:
|
||||||
|
enabled: true
|
||||||
|
ipxe:
|
||||||
|
"00:00": undionly.kpxe
|
||||||
|
"00:07": ipxe-snponly-x86_64.efi
|
||||||
|
"00:09": ipxe-snponly-x86_64.efi
|
||||||
|
"00:0B": arm64-efi/snponly.efi
|
||||||
|
systemd name: tftp
|
||||||
|
tftproot: /var/lib/tftpboot
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"ipv6 cidr conflict": {
|
||||||
|
input: `
|
||||||
|
ipaddr6: "2001:db8:1::1/64"
|
||||||
|
ipv6net: "2001:db8:2::"
|
||||||
|
`,
|
||||||
|
result: `
|
||||||
|
ipaddr6: "2001:db8:1::1/64"
|
||||||
|
ipv6net: "2001:db8:2::"
|
||||||
|
warewulf:
|
||||||
|
autobuild overlays: true
|
||||||
|
grubboot: false
|
||||||
|
host overlay: true
|
||||||
|
port: 9873
|
||||||
|
secure: true
|
||||||
|
update interval: 60
|
||||||
|
nfs:
|
||||||
|
enabled: true
|
||||||
|
systemd name: nfsd
|
||||||
|
dhcp:
|
||||||
|
enabled: true
|
||||||
|
systemd name: dhcpd
|
||||||
|
template: default
|
||||||
|
image mounts:
|
||||||
|
- dest: /etc/resolv.conf
|
||||||
|
source: /etc/resolv.conf
|
||||||
|
paths:
|
||||||
|
bindir: /usr/local/bin
|
||||||
|
cachedir: /var/local/cache
|
||||||
|
datadir: /usr/local/share
|
||||||
|
firewallddir: /usr/lib/firewalld/services
|
||||||
|
ipxesource: /usr/local/share/ipxe
|
||||||
|
localstatedir: /var/local
|
||||||
|
srvdir: /srv
|
||||||
|
sysconfdir: /usr/local/etc
|
||||||
|
systemddir: /usr/lib/systemd/system
|
||||||
|
wwchrootdir: /var/local/warewulf/chroots
|
||||||
|
wwclientdir: /warewulf
|
||||||
|
wwoverlaydir: /var/local/warewulf/overlays
|
||||||
|
wwprovisiondir: /var/local/warewulf/provision
|
||||||
|
ssh:
|
||||||
|
key types:
|
||||||
|
- ed25519
|
||||||
|
- ecdsa
|
||||||
|
- rsa
|
||||||
|
- dsa
|
||||||
|
tftp:
|
||||||
|
enabled: true
|
||||||
|
ipxe:
|
||||||
|
"00:00": undionly.kpxe
|
||||||
|
"00:07": ipxe-snponly-x86_64.efi
|
||||||
|
"00:09": ipxe-snponly-x86_64.efi
|
||||||
|
"00:0B": arm64-efi/snponly.efi
|
||||||
|
systemd name: tftp
|
||||||
|
tftproot: /var/lib/tftpboot
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
input: `
|
||||||
ipaddr: 192.168.200.1
|
ipaddr: 192.168.200.1
|
||||||
netmask: 255.255.255.0
|
netmask: 255.255.255.0
|
||||||
network: 192.168.200.0
|
network: 192.168.200.0
|
||||||
@@ -98,8 +318,84 @@ nfs:
|
|||||||
image mounts:
|
image mounts:
|
||||||
- source: /etc/resolv.conf
|
- source: /etc/resolv.conf
|
||||||
dest: /etc/resolv.conf
|
dest: /etc/resolv.conf
|
||||||
readonly: true`
|
readonly: true
|
||||||
|
`,
|
||||||
|
result: `
|
||||||
|
ipaddr: 192.168.200.1
|
||||||
|
netmask: 255.255.255.0
|
||||||
|
network: 192.168.200.0
|
||||||
|
warewulf:
|
||||||
|
autobuild overlays: true
|
||||||
|
grubboot: false
|
||||||
|
host overlay: true
|
||||||
|
port: 9873
|
||||||
|
secure: false
|
||||||
|
update interval: 60
|
||||||
|
nfs:
|
||||||
|
enabled: true
|
||||||
|
systemd name: nfs-server
|
||||||
|
export paths:
|
||||||
|
- path: /home
|
||||||
|
export options: rw,sync
|
||||||
|
- path: /opt
|
||||||
|
export options: ro,sync,no_root_squash
|
||||||
|
dhcp:
|
||||||
|
enabled: true
|
||||||
|
systemd name: dhcpd
|
||||||
|
template: default
|
||||||
|
range end: 192.168.200.99
|
||||||
|
range start: 192.168.200.50
|
||||||
|
image mounts:
|
||||||
|
- dest: /etc/resolv.conf
|
||||||
|
readonly: true
|
||||||
|
source: /etc/resolv.conf
|
||||||
|
paths:
|
||||||
|
bindir: /usr/local/bin
|
||||||
|
cachedir: /var/local/cache
|
||||||
|
datadir: /usr/local/share
|
||||||
|
firewallddir: /usr/lib/firewalld/services
|
||||||
|
ipxesource: /usr/local/share/ipxe
|
||||||
|
localstatedir: /var/local
|
||||||
|
srvdir: /srv
|
||||||
|
sysconfdir: /usr/local/etc
|
||||||
|
systemddir: /usr/lib/systemd/system
|
||||||
|
wwchrootdir: /var/local/warewulf/chroots
|
||||||
|
wwclientdir: /warewulf
|
||||||
|
wwoverlaydir: /var/local/warewulf/overlays
|
||||||
|
wwprovisiondir: /var/local/warewulf/provision
|
||||||
|
ssh:
|
||||||
|
key types:
|
||||||
|
- ed25519
|
||||||
|
- ecdsa
|
||||||
|
- rsa
|
||||||
|
- dsa
|
||||||
|
tftp:
|
||||||
|
enabled: true
|
||||||
|
ipxe:
|
||||||
|
"00:00": undionly.kpxe
|
||||||
|
"00:07": ipxe-snponly-x86_64.efi
|
||||||
|
"00:09": ipxe-snponly-x86_64.efi
|
||||||
|
"00:0B": arm64-efi/snponly.efi
|
||||||
|
systemd name: tftp
|
||||||
|
tftproot: /var/lib/tftpboot
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
conf := New()
|
||||||
|
err := conf.Parse([]byte(tt.input))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
result, err := conf.Dump()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.YAMLEq(t, tt.result, string(result))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitializedFromFile(t *testing.T) {
|
||||||
|
example_warewulf_conf := ""
|
||||||
tempWarewulfConf, warewulfConfErr := os.CreateTemp("", "warewulf.conf-")
|
tempWarewulfConf, warewulfConfErr := os.CreateTemp("", "warewulf.conf-")
|
||||||
assert.NoError(t, warewulfConfErr)
|
assert.NoError(t, warewulfConfErr)
|
||||||
defer os.Remove(tempWarewulfConf.Name())
|
defer os.Remove(tempWarewulfConf.Name())
|
||||||
@@ -108,36 +404,10 @@ image mounts:
|
|||||||
assert.NoError(t, tempWarewulfConf.Sync())
|
assert.NoError(t, tempWarewulfConf.Sync())
|
||||||
|
|
||||||
conf := New()
|
conf := New()
|
||||||
|
assert.False(t, conf.InitializedFromFile())
|
||||||
assert.NoError(t, conf.Read(tempWarewulfConf.Name()))
|
assert.NoError(t, conf.Read(tempWarewulfConf.Name()))
|
||||||
|
assert.True(t, conf.InitializedFromFile())
|
||||||
assert.Equal(t, "192.168.200.1", conf.Ipaddr)
|
assert.Equal(t, conf.GetWarewulfConf(), tempWarewulfConf.Name())
|
||||||
assert.Equal(t, "255.255.255.0", conf.Netmask)
|
|
||||||
assert.Equal(t, "192.168.200.0", conf.Network)
|
|
||||||
|
|
||||||
assert.Equal(t, 9873, conf.Warewulf.Port)
|
|
||||||
assert.False(t, conf.Warewulf.Secure())
|
|
||||||
assert.Equal(t, 60, conf.Warewulf.UpdateInterval)
|
|
||||||
assert.True(t, conf.Warewulf.AutobuildOverlays())
|
|
||||||
assert.True(t, conf.Warewulf.EnableHostOverlay())
|
|
||||||
|
|
||||||
assert.True(t, conf.DHCP.Enabled())
|
|
||||||
assert.Equal(t, "192.168.200.50", conf.DHCP.RangeStart)
|
|
||||||
assert.Equal(t, "192.168.200.99", conf.DHCP.RangeEnd)
|
|
||||||
assert.Equal(t, "dhcpd", conf.DHCP.SystemdName)
|
|
||||||
|
|
||||||
assert.True(t, conf.TFTP.Enabled())
|
|
||||||
assert.Equal(t, "tftp", conf.TFTP.SystemdName)
|
|
||||||
|
|
||||||
assert.True(t, conf.NFS.Enabled())
|
|
||||||
assert.Equal(t, "/home", conf.NFS.ExportsExtended[0].Path)
|
|
||||||
assert.Equal(t, "rw,sync", conf.NFS.ExportsExtended[0].ExportOptions)
|
|
||||||
assert.Equal(t, "/opt", conf.NFS.ExportsExtended[1].Path)
|
|
||||||
assert.Equal(t, "ro,sync,no_root_squash", conf.NFS.ExportsExtended[1].ExportOptions)
|
|
||||||
assert.Equal(t, "nfs-server", conf.NFS.SystemdName)
|
|
||||||
|
|
||||||
assert.Equal(t, "/etc/resolv.conf", conf.MountsImage[0].Source)
|
|
||||||
assert.Equal(t, "/etc/resolv.conf", conf.MountsImage[0].Dest)
|
|
||||||
assert.True(t, conf.MountsImage[0].ReadOnly())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
@@ -154,3 +424,99 @@ func TestCache(t *testing.T) {
|
|||||||
New()
|
New()
|
||||||
assert.NotEqual(t, 9999, Get().Warewulf.Port)
|
assert.NotEqual(t, 9999, Get().Warewulf.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIpCIDR(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
ipaddr string
|
||||||
|
netmask string
|
||||||
|
cidr string
|
||||||
|
}{
|
||||||
|
"blank": {
|
||||||
|
ipaddr: "",
|
||||||
|
netmask: "",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"ip only": {
|
||||||
|
ipaddr: "192.168.0.1",
|
||||||
|
netmask: "",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"netmask only": {
|
||||||
|
ipaddr: "",
|
||||||
|
netmask: "255.255.255.0",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"full": {
|
||||||
|
ipaddr: "192.168.0.1",
|
||||||
|
netmask: "255.255.255.0",
|
||||||
|
cidr: "192.168.0.1/24",
|
||||||
|
},
|
||||||
|
"invalid ip": {
|
||||||
|
ipaddr: "asdf",
|
||||||
|
netmask: "255.255.255.0",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"invalid netmask": {
|
||||||
|
ipaddr: "192.168.0.1",
|
||||||
|
netmask: "asdf",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
conf := New()
|
||||||
|
conf.Ipaddr = tt.ipaddr
|
||||||
|
conf.Netmask = tt.netmask
|
||||||
|
assert.Equal(t, tt.cidr, conf.IpCIDR())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkCIDR(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
network string
|
||||||
|
netmask string
|
||||||
|
cidr string
|
||||||
|
}{
|
||||||
|
"blank": {
|
||||||
|
network: "",
|
||||||
|
netmask: "",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"network only": {
|
||||||
|
network: "192.168.0.0",
|
||||||
|
netmask: "",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"netmask only": {
|
||||||
|
network: "",
|
||||||
|
netmask: "255.255.255.0",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"full": {
|
||||||
|
network: "192.168.0.0",
|
||||||
|
netmask: "255.255.255.0",
|
||||||
|
cidr: "192.168.0.0/24",
|
||||||
|
},
|
||||||
|
"invalid network": {
|
||||||
|
network: "asdf",
|
||||||
|
netmask: "255.255.255.0",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
"invalid netmask": {
|
||||||
|
network: "192.168.0.0",
|
||||||
|
netmask: "asdf",
|
||||||
|
cidr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
conf := New()
|
||||||
|
conf.Network = tt.network
|
||||||
|
conf.Netmask = tt.netmask
|
||||||
|
assert.Equal(t, tt.cidr, conf.NetworkCIDR())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type TemplateStruct struct {
|
|||||||
BuildTimeUnix string
|
BuildTimeUnix string
|
||||||
BuildSource string
|
BuildSource string
|
||||||
Ipaddr string
|
Ipaddr string
|
||||||
|
IpCIDR string
|
||||||
Ipaddr6 string
|
Ipaddr6 string
|
||||||
Netmask string
|
Netmask string
|
||||||
Network string
|
Network string
|
||||||
@@ -67,9 +68,11 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (T
|
|||||||
tstruct.Paths = *controller.Paths
|
tstruct.Paths = *controller.Paths
|
||||||
tstruct.Warewulf = *controller.Warewulf
|
tstruct.Warewulf = *controller.Warewulf
|
||||||
tstruct.Ipaddr = controller.Ipaddr
|
tstruct.Ipaddr = controller.Ipaddr
|
||||||
|
tstruct.IpCIDR = controller.IpCIDR()
|
||||||
tstruct.Ipaddr6 = controller.Ipaddr6
|
tstruct.Ipaddr6 = controller.Ipaddr6
|
||||||
tstruct.Netmask = controller.Netmask
|
tstruct.Netmask = controller.Netmask
|
||||||
tstruct.Network = controller.Network
|
tstruct.Network = controller.Network
|
||||||
|
tstruct.NetworkCIDR = controller.NetworkCIDR()
|
||||||
// init some convenience vars
|
// init some convenience vars
|
||||||
tstruct.Id = nodeData.Id()
|
tstruct.Id = nodeData.Id()
|
||||||
tstruct.Hostname = nodeData.Id()
|
tstruct.Hostname = nodeData.Id()
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ func Test_debugOverlay(t *testing.T) {
|
|||||||
|
|
||||||
env := testenv.New(t)
|
env := testenv.New(t)
|
||||||
defer env.RemoveAll()
|
defer env.RemoveAll()
|
||||||
|
env.ImportFile("etc/warewulf/warewulf.conf", "warewulf.conf")
|
||||||
env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf")
|
env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf")
|
||||||
env.ImportFile("var/lib/warewulf/overlays/debug/rootfs/tstruct.md.ww", "../rootfs/tstruct.md.ww")
|
env.ImportFile("var/lib/warewulf/overlays/debug/rootfs/tstruct.md.ww", "../rootfs/tstruct.md.ww")
|
||||||
|
env.Configure()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -86,11 +88,12 @@ data from other structures.
|
|||||||
|
|
||||||
### Network
|
### Network
|
||||||
|
|
||||||
- Ipaddr:
|
- Ipaddr: 192.168.0.1
|
||||||
|
- IpCIDR: 192.168.0.1/24
|
||||||
- Ipaddr6:
|
- Ipaddr6:
|
||||||
- Netmask:
|
- Netmask: 255.255.255.0
|
||||||
- Network:
|
- Network: 192.168.0.0
|
||||||
- NetworkCIDR:
|
- NetworkCIDR: 192.168.0.0/24
|
||||||
- Ipv6: false
|
- Ipv6: false
|
||||||
|
|
||||||
### DHCP
|
### DHCP
|
||||||
|
|||||||
1
overlays/debug/internal/warewulf.conf
Normal file
1
overlays/debug/internal/warewulf.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ipaddr: 192.168.0.1/24
|
||||||
@@ -29,6 +29,7 @@ data from other structures.
|
|||||||
### Network
|
### Network
|
||||||
|
|
||||||
- Ipaddr: {{ .Ipaddr }}
|
- Ipaddr: {{ .Ipaddr }}
|
||||||
|
- IpCIDR: {{ .IpCIDR }}
|
||||||
- Ipaddr6: {{ .Ipaddr6 }}
|
- Ipaddr6: {{ .Ipaddr6 }}
|
||||||
- Netmask: {{ .Netmask }}
|
- Netmask: {{ .Netmask }}
|
||||||
- Network: {{ .Network }}
|
- Network: {{ .Network }}
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ option architecture-type code 93 = unsigned integer 16;
|
|||||||
if exists user-class and option user-class = "iPXE" {
|
if exists user-class and option user-class = "iPXE" {
|
||||||
filename "http://192.168.0.1:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}";
|
filename "http://192.168.0.1:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}";
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if option architecture-type = 00:00 {
|
if option architecture-type = 00:00 {
|
||||||
filename "/warewulf/undionly.kpxe";
|
filename "/warewulf/undionly.kpxe";
|
||||||
}
|
}
|
||||||
@@ -166,7 +165,6 @@ if exists user-class and option user-class = "iPXE" {
|
|||||||
filename "/warewulf/snponly.efi";
|
filename "/warewulf/snponly.efi";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subnet 192.168.0.0 netmask 255.255.255.0 {
|
subnet 192.168.0.0 netmask 255.255.255.0 {
|
||||||
max-lease-time 120;
|
max-lease-time 120;
|
||||||
range 192.168.0.100 192.168.0.199;
|
range 192.168.0.100 192.168.0.199;
|
||||||
@@ -201,7 +199,6 @@ option architecture-type code 93 = unsigned integer 16;
|
|||||||
if exists user-class and option user-class = "iPXE" {
|
if exists user-class and option user-class = "iPXE" {
|
||||||
filename "http://192.168.0.1:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}";
|
filename "http://192.168.0.1:9873/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}";
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if option architecture-type = 00:00 {
|
if option architecture-type = 00:00 {
|
||||||
filename "/warewulf/undionly.kpxe";
|
filename "/warewulf/undionly.kpxe";
|
||||||
}
|
}
|
||||||
@@ -215,7 +212,6 @@ if exists user-class and option user-class = "iPXE" {
|
|||||||
filename "/warewulf/snponly.efi";
|
filename "/warewulf/snponly.efi";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subnet 192.168.0.0 netmask 255.255.255.0 {
|
subnet 192.168.0.0 netmask 255.255.255.0 {
|
||||||
max-lease-time 120;
|
max-lease-time 120;
|
||||||
range 192.168.0.100 192.168.0.199;
|
range 192.168.0.100 192.168.0.199;
|
||||||
@@ -278,7 +274,6 @@ dhcp-host=e6:92:39:49:7b:04,set:warewulf,node2,192.168.3.23,infinite
|
|||||||
const host_exports string = `backupFile: true
|
const host_exports string = `backupFile: true
|
||||||
writeFile: true
|
writeFile: true
|
||||||
Filename: etc/exports
|
Filename: etc/exports
|
||||||
|
|
||||||
# This file is autogenerated by warewulf
|
# This file is autogenerated by warewulf
|
||||||
/home 192.168.0.0/255.255.255.0(rw,sync)
|
/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)
|
/opt 192.168.0.0/255.255.255.0(ro,sync,no_root_squash)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{{if $.Dhcp.Enabled -}}
|
{{ if $.Dhcp.Enabled -}}
|
||||||
# This file is autogenerated by warewulf
|
# This file is autogenerated by warewulf
|
||||||
|
|
||||||
allow booting;
|
allow booting;
|
||||||
@@ -38,19 +38,21 @@ if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
|
|||||||
if exists user-class and option user-class = "iPXE" {
|
if exists user-class and option user-class = "iPXE" {
|
||||||
filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}";
|
filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/ipxe/${mac:hexhyp}?assetkey=${asset}&uuid=${uuid}";
|
||||||
} else {
|
} else {
|
||||||
{{range $type,$name := $.Tftp.IpxeBinaries }}
|
{{- range $type, $name := $.Tftp.IpxeBinaries }}
|
||||||
if option architecture-type = {{ $type }} {
|
if option architecture-type = {{ $type }} {
|
||||||
filename "/warewulf/{{ basename $name }}";
|
filename "/warewulf/{{ basename $name }}";
|
||||||
}
|
}
|
||||||
{{- end }}{{/* range IpxeBinaries */}}
|
{{- end }}
|
||||||
}
|
}
|
||||||
{{- end }}{{/* BootMethod */}}
|
{{- end }}
|
||||||
|
|
||||||
subnet {{$.Network}} netmask {{$.Netmask}} {
|
{{- if and .Network .Netmask .Dhcp.RangeStart .Dhcp.RangeEnd }}
|
||||||
|
subnet {{.Network}} netmask {{.Netmask}} {
|
||||||
max-lease-time 120;
|
max-lease-time 120;
|
||||||
range {{$.Dhcp.RangeStart}} {{$.Dhcp.RangeEnd}};
|
range {{.Dhcp.RangeStart}} {{.Dhcp.RangeEnd}};
|
||||||
next-server {{.Ipaddr}};
|
next-server {{.Ipaddr}};
|
||||||
}
|
}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{- if eq .Dhcp.Template "static" }}
|
{{- if eq .Dhcp.Template "static" }}
|
||||||
{{- range $nodes := $.AllNodes}}
|
{{- range $nodes := $.AllNodes}}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{{- if .Nfs.Enabled }}
|
{{ if .Nfs.Enabled -}}
|
||||||
# This file is autogenerated by warewulf
|
# This file is autogenerated by warewulf
|
||||||
{{- $network := .Network }}
|
{{- $network := .Network }}
|
||||||
{{- $netmask := .Netmask }}
|
{{- $netmask := .Netmask }}
|
||||||
|
|||||||
Reference in New Issue
Block a user