Files
warewulf/internal/pkg/config/root.go
Jonathon Anderson 387cd0f7be Refactor BaseConf Read and Parse
Also refactor New and Get to return pointers to match BaseConf methods.
This makes calling the methods immediately on the return values of the
constructors easier.

Also move config.ConfigFile to buildconfig.go.in. ConfigFile is still
used by wwctl as a default config file to reference; but it is removed
from other locations now.

Removed Persist, as nothing called it.

Signed-off-by: Jonathon Anderson <janderson@ciq.co>
2023-04-19 14:02:43 -06:00

187 lines
5.6 KiB
Go

// Package config reads, parses, and represents the warewulf.conf
// config file.
//
// warewulf.conf is a yaml-formatted configuration file that includes
// configuration for the Warewulf daemon and commands, as well as the
// DHCP, TFTP and NFS services that Warewulf manages.
package config
import (
"fmt"
"net"
"os"
"reflect"
"github.com/pkg/errors"
"github.com/creasty/defaults"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"gopkg.in/yaml.v2"
)
var cachedConf RootConf
// RootConf is the main Warewulf configuration structure. It stores
// some information about the Warewulf server locally, and has
// [WarewulfConf], [DhcpConf], [TftpConf], and [NfsConf] sub-sections.
type RootConf struct {
WWInternal int `yaml:"WW_INTERNAL"`
Comment string `yaml:"comment,omitempty"`
Ipaddr string `yaml:"ipaddr"`
Ipaddr6 string `yaml:"ipaddr6,omitempty"`
Netmask string `yaml:"netmask"`
Network string `yaml:"network,omitempty"`
Ipv6net string `yaml:"ipv6net,omitempty"`
Fqdn string `yaml:"fqdn,omitempty"`
Warewulf *WarewulfConf `yaml:"warewulf"`
Dhcp *DhcpConf `yaml:"dhcp"`
Tftp *TftpConf `yaml:"tftp"`
Nfs *NfsConf `yaml:"nfs"`
MountsContainer []*MountEntry `yaml:"container mounts" default:"[{\"source\": \"/etc/resolv.conf\", \"dest\": \"/etc/resolv.conf\"}]"`
Paths *BuildConfig `yaml:"paths"`
fromFile bool
}
// New caches and returns a new [RootConf] initialized with empty
// values, clearing replacing any previously cached value.
func New() (*RootConf) {
cachedConf = RootConf{}
cachedConf.fromFile = false
cachedConf.Warewulf = new(WarewulfConf)
cachedConf.Dhcp = new(DhcpConf)
cachedConf.Tftp = new(TftpConf)
cachedConf.Nfs = new(NfsConf)
cachedConf.Paths = new(BuildConfig)
if err := defaults.Set(&cachedConf); err != nil {
panic(err)
}
return &cachedConf
}
// Get returns a previously cached [RootConf] if it exists, or returns
// a new RootConf.
func Get() (*RootConf) {
// NOTE: This function can be called before any log level is set
// so using wwlog.Verbose or wwlog.Debug won't work
if reflect.ValueOf(cachedConf).IsZero() {
cachedConf = *New()
}
return &cachedConf
}
// ReadConf populates [RootConf] with the values from a configuration
// file.
func (conf *RootConf) Read(confFileName string) (error) {
wwlog.Debug("Reading warewulf.conf from: %s", confFileName)
data, err := os.ReadFile(confFileName)
if err != nil {
return err
}
return conf.Parse(data)
}
// Read populates [RootConf] with the values from a yaml document.
func (conf *RootConf) Parse(data []byte) (error) {
// ipxe binaries are merged not overwritten, store defaults separate
defIpxe := make(map[string]string)
for k, v := range conf.Tftp.IpxeBinaries {
defIpxe[k] = v
delete(conf.Tftp.IpxeBinaries, k)
}
if err := yaml.Unmarshal(data, &conf); err != nil {
return err
}
if err := conf.SetDynamicDefaults(); err != nil {
return err
}
if len(conf.Tftp.IpxeBinaries) == 0 {
conf.Tftp.IpxeBinaries = defIpxe
}
cachedConf = *conf
cachedConf.fromFile = true
return nil
}
// SetDynamicDefaults populates [RootConf] with plausible defaults for
// the runtime environment.
func (conf *RootConf) SetDynamicDefaults() (err error) {
if conf.Ipaddr == "" || conf.Netmask == "" || conf.Network == "" {
var mask net.IPMask
var network *net.IPNet
var ipaddr net.IP
if conf.Ipaddr == "" {
wwlog.Verbose("Configuration has no valid network, going to dynamic values")
conn, _ := net.Dial("udp", "8.8.8.8:80")
defer conn.Close()
ipaddr = conn.LocalAddr().(*net.UDPAddr).IP
mask = ipaddr.DefaultMask()
sz, _ := mask.Size()
conf.Ipaddr = ipaddr.String() + fmt.Sprintf("/%d", sz)
}
_, network, err = net.ParseCIDR(conf.Ipaddr)
if err == nil {
mask = network.Mask
} else {
return errors.Wrap(err, "Couldn't parse IP address")
}
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)
}
}
if conf.Dhcp.RangeStart == "" && conf.Dhcp.RangeEnd == "" {
start := net.ParseIP(conf.Network).To4()
start[3] += 1
if start.Equal(net.ParseIP(conf.Ipaddr)) {
start[3] += 1
}
conf.Dhcp.RangeStart = start.String()
wwlog.Verbose("dhpd start is not configured in warewulf.conf, using %s", conf.Dhcp.RangeStart)
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)
}
// check validity of ipv6 net
if conf.Ipaddr6 != "" {
_, ipv6net, err := net.ParseCIDR(conf.Ipaddr6)
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")
}
}
cachedConf = *conf
return
}
// InitializedFromFile returns true if [RootConf] memory was read from
// a file, or false otherwise.
func (conf *RootConf) InitializedFromFile() bool {
return conf.fromFile
}