262 lines
6.9 KiB
Go
262 lines
6.9 KiB
Go
// Package config reads, parses, and represents the sunhpc.conf
|
|
// config file.
|
|
//
|
|
// sunhpc.conf is a yaml-formatted configuration file that includes
|
|
// configuration for the sunhpc daemon and commands, as well as the
|
|
// DHCP, TFTP and NFS services that sunhpc manages.
|
|
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"github.com/creasty/defaults"
|
|
"gitea.sunhpc.com/kelvin/sunhpc/internal/pkg/splog"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var cachedConf SunhpcYaml
|
|
|
|
// SunhpcYaml is the main Sunhpc configuration structure. It stores
|
|
// some information about the Sunhpc server locally, and has
|
|
// [SunhpcConf], [DHCPConf], [TFTPConf], and [NFSConf] sub-sections.
|
|
type SunhpcYaml struct {
|
|
Comment string `yaml:"comment,omitempty"`
|
|
Ipaddr string `yaml:"ipaddr,omitempty"`
|
|
Netmask string `yaml:"netmask,omitempty"`
|
|
Network string `yaml:"network,omitempty"`
|
|
Fqdn string `yaml:"fqdn,omitempty"`
|
|
Ipaddr6 string `yaml:"ipaddr6,omitempty"`
|
|
PrefixLen6 string `yaml:"prefixlen6,omitempty"`
|
|
Sunhpc *SunhpcConf `yaml:"sunhpc,omitempty"`
|
|
API *APIConf `yaml:"api,omitempty"`
|
|
DNSMASQ *DNSMASQConf `yaml:"dhcp,omitempty"`
|
|
DHCP *DHCPConf `yaml:"dhcp,omitempty"`
|
|
TFTP *TFTPConf `yaml:"tftp,omitempty"`
|
|
NFS *NFSConf `yaml:"nfs,omitempty"`
|
|
SSH *SSHConf `yaml:"ssh,omitempty"`
|
|
MountsImage []*MountEntry `yaml:"image mounts,omitempty" default:"[{\"source\": \"/etc/resolv.conf\", \"dest\": \"/etc/resolv.conf\"}]"`
|
|
Paths *BuildConfig `yaml:"paths,omitempty"`
|
|
//Client *SPClientConf `yaml:"client,omitempty"`
|
|
|
|
sunhpcconf string
|
|
autodetected bool
|
|
}
|
|
|
|
// New caches and returns a new [SunhpcYaml] initialized with empty
|
|
// values, clearing replacing any previously cached value.
|
|
func New() *SunhpcYaml {
|
|
cachedConf = SunhpcYaml{}
|
|
cachedConf.sunhpcconf = ""
|
|
|
|
cachedConf.Sunhpc = new(SunhpcConf)
|
|
cachedConf.DNSMASQ = new(DNSMASQConf)
|
|
cachedConf.Paths = new(BuildConfig)
|
|
cachedConf.DHCP = new(DHCPConf)
|
|
cachedConf.TFTP = new(TFTPConf)
|
|
cachedConf.NFS = new(NFSConf)
|
|
cachedConf.SSH = new(SSHConf)
|
|
cachedConf.API = new(APIConf)
|
|
if err := defaults.Set(&cachedConf); err != nil {
|
|
panic(err)
|
|
}
|
|
return &cachedConf
|
|
}
|
|
|
|
// Get returns a previously cached [SunhpcYaml] if it exists, or returns
|
|
// a new SunhpcYaml.
|
|
func Get() *SunhpcYaml {
|
|
// NOTE: This function can be called before any log level is set
|
|
// so using splog.Verbose or splog.Debug won't work
|
|
if reflect.ValueOf(cachedConf).IsZero() {
|
|
cachedConf = *New()
|
|
}
|
|
return &cachedConf
|
|
}
|
|
|
|
// Read populates [SunhpcYaml] with the values from a configuration
|
|
// file.
|
|
func (conf *SunhpcYaml) Read(confFileName string, autodetect bool) error {
|
|
splog.Debug("Reading sunhpc.conf from: %s", confFileName)
|
|
conf.sunhpcconf = confFileName
|
|
if data, err := os.ReadFile(confFileName); err != nil {
|
|
return err
|
|
} else if err := conf.Parse(data, autodetect); err != nil {
|
|
return err
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Parse populates [SunhpcYaml] with the values from a yaml document.
|
|
func (conf *SunhpcYaml) Parse(data []byte, autodetect bool) 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 len(conf.TFTP.IpxeBinaries) == 0 {
|
|
conf.TFTP.IpxeBinaries = defIpxe
|
|
}
|
|
|
|
if ip, network, err := net.ParseCIDR(conf.Ipaddr); err == nil {
|
|
conf.Ipaddr = ip.String()
|
|
if conf.Network == "" {
|
|
conf.Network = network.IP.String()
|
|
}
|
|
if conf.Netmask == "" {
|
|
conf.Netmask = net.IP(network.Mask).String()
|
|
}
|
|
}
|
|
|
|
if autodetect {
|
|
if conf.Ipaddr == "" {
|
|
if ip := GetOutboundIP(); ip != nil {
|
|
conf.Ipaddr = ip.String()
|
|
conf.autodetected = true
|
|
}
|
|
}
|
|
|
|
if conf.Netmask == "" {
|
|
if ip := net.ParseIP(conf.Ipaddr); ip != nil {
|
|
if network, err := GetIPNetForIP(ip); err == nil {
|
|
conf.Netmask = net.IP(network.Mask).String()
|
|
conf.autodetected = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if conf.Network == "" {
|
|
if ip := net.ParseIP(conf.Ipaddr); ip != nil {
|
|
if mask := net.IPMask(net.ParseIP(conf.Netmask)); mask != nil {
|
|
conf.Network = ip.Mask(mask).String()
|
|
conf.autodetected = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ip := net.ParseIP(conf.Ipaddr6); ip != nil {
|
|
if prefix, err := strconv.Atoi(conf.PrefixLen6); err == nil {
|
|
conf.Ipaddr6 = ip.String()
|
|
conf.PrefixLen6 = strconv.Itoa(prefix)
|
|
}
|
|
}
|
|
|
|
if ip, net, err := net.ParseCIDR(conf.Ipaddr6); err == nil {
|
|
conf.Ipaddr6 = ip.String()
|
|
prefixLen6, _ := net.Mask.Size()
|
|
conf.PrefixLen6 = strconv.Itoa(prefixLen6)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (config *SunhpcYaml) Network6() string {
|
|
cidr := fmt.Sprintf("%s/%s", config.Ipaddr6, config.PrefixLen6)
|
|
_, ipnet, err := net.ParseCIDR(cidr)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return ipnet.IP.String()
|
|
}
|
|
|
|
func (config *SunhpcYaml) NetworkCIDR() string {
|
|
if config.Network == "" || config.Netmask == "" {
|
|
return ""
|
|
}
|
|
cidr := net.IPNet{
|
|
IP: net.ParseIP(config.Network),
|
|
Mask: net.IPMask(net.ParseIP(config.Netmask)),
|
|
}
|
|
if cidr.IP == nil || cidr.Mask == nil {
|
|
return ""
|
|
}
|
|
return cidr.String()
|
|
}
|
|
|
|
func (config *SunhpcYaml) NetworkCIDR6() string {
|
|
cidr := fmt.Sprintf("%s/%s", config.Ipaddr6, config.PrefixLen6)
|
|
_, ipnet, err := net.ParseCIDR(cidr)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return ipnet.String()
|
|
}
|
|
|
|
func (config *SunhpcYaml) IpCIDR() string {
|
|
if config.Ipaddr == "" || config.Netmask == "" {
|
|
return ""
|
|
}
|
|
cidr := net.IPNet{
|
|
IP: net.ParseIP(config.Ipaddr),
|
|
Mask: net.IPMask(net.ParseIP(config.Netmask)),
|
|
}
|
|
if cidr.IP == nil || cidr.Mask == nil {
|
|
return ""
|
|
}
|
|
return cidr.String()
|
|
}
|
|
|
|
func (config *SunhpcYaml) IpCIDR6() string {
|
|
cidr := fmt.Sprintf("%s/%s", config.Ipaddr6, config.PrefixLen6)
|
|
ip, _, err := net.ParseCIDR(cidr)
|
|
if err != nil || ip == nil || ip.To4() != nil {
|
|
return ""
|
|
}
|
|
return cidr
|
|
}
|
|
|
|
// InitializedFromFile returns true if [SunhpcYaml] memory was read from
|
|
// a file, or false otherwise.
|
|
func (conf *SunhpcYaml) InitializedFromFile() bool {
|
|
return conf.sunhpcconf != ""
|
|
}
|
|
|
|
func (conf *SunhpcYaml) GetSunhpcConf() string {
|
|
return conf.sunhpcconf
|
|
}
|
|
|
|
func (conf *SunhpcYaml) Autodetected() bool {
|
|
return conf.autodetected
|
|
}
|
|
|
|
func (config *SunhpcYaml) Dump() ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
yamlEncoder := yaml.NewEncoder(&buf)
|
|
yamlEncoder.SetIndent(2)
|
|
err := yamlEncoder.Encode(config)
|
|
return buf.Bytes(), err
|
|
}
|
|
|
|
func (config *SunhpcYaml) PersistToFile(configFile string) (err error) {
|
|
out, dumpErr := config.Dump()
|
|
if dumpErr != nil {
|
|
splog.Error("%s", dumpErr)
|
|
return dumpErr
|
|
}
|
|
file, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
|
|
if err != nil {
|
|
splog.Error("%s", err)
|
|
return err
|
|
}
|
|
defer func() {
|
|
if cerr := file.Close(); cerr != nil && err == nil {
|
|
err = cerr
|
|
}
|
|
}()
|
|
_, err = file.WriteString(string(out))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
splog.Debug("persisted: %s", configFile)
|
|
return nil
|
|
} |