feat: initial release
This commit is contained in:
parent
a3cf21f5bd
commit
761174d035
41 changed files with 2008 additions and 217 deletions
95
internal/config/config.go
Normal file
95
internal/config/config.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.wzray.com/homelab/mastermind/internal/types"
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type Configs struct {
|
||||
Master MasterConfig
|
||||
Dns DnsConfig
|
||||
Host HostConfig
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Node NodeConfig
|
||||
Configs Configs
|
||||
Roles []types.Role
|
||||
}
|
||||
|
||||
func FromFile(filename string) (Config, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("read config file: %w", err)
|
||||
}
|
||||
|
||||
var temp struct {
|
||||
Node NodeConfig `toml:"node"`
|
||||
Configs struct {
|
||||
Host *HostConfig `toml:"host"`
|
||||
Dns *DnsConfig `toml:"dns"`
|
||||
Master *MasterConfig `toml:"master"`
|
||||
} `toml:"roles"`
|
||||
}
|
||||
|
||||
if err := toml.Unmarshal(data, &temp); err != nil {
|
||||
return Config{}, fmt.Errorf("parse config file: %w", err)
|
||||
}
|
||||
|
||||
config := defaultConfig
|
||||
config.Node.Merge(temp.Node)
|
||||
|
||||
if c := temp.Configs.Master; c != nil {
|
||||
c.set = true
|
||||
config.Roles = append(config.Roles, types.MasterRole)
|
||||
config.Configs.Master.Merge(*c)
|
||||
}
|
||||
|
||||
if c := temp.Configs.Dns; c != nil {
|
||||
c.set = true
|
||||
config.Roles = append(config.Roles, types.DnsRole)
|
||||
config.Configs.Dns.Merge(*c)
|
||||
}
|
||||
|
||||
if c := temp.Configs.Host; c != nil {
|
||||
c.set = true
|
||||
config.Roles = append(config.Roles, types.HostRole)
|
||||
config.Configs.Host.Merge(*c)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if err := c.Node.Validate(); err != nil {
|
||||
return fmt.Errorf("node: %w", err)
|
||||
}
|
||||
|
||||
if c.Configs.Host.set {
|
||||
if err := c.Configs.Host.Validate(); err != nil {
|
||||
return fmt.Errorf("configs.host: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Configs.Dns.set {
|
||||
if err := c.Configs.Dns.Validate(); err != nil {
|
||||
return fmt.Errorf("configs.dns: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Configs.Host.set {
|
||||
if err := c.Configs.Master.Validate(); err != nil {
|
||||
return fmt.Errorf("configs.master: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Roles) == 0 {
|
||||
return errors.New("no roles configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
17
internal/config/defaults.go
Normal file
17
internal/config/defaults.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package config
|
||||
|
||||
var defaultConfig = Config{
|
||||
Node: NodeConfig{
|
||||
ListenOn: "0.0.0.0",
|
||||
Port: 56714,
|
||||
KeepaliveInterval: 1,
|
||||
LogLevel: LogLevelInfo,
|
||||
},
|
||||
Configs: Configs{
|
||||
Master: MasterConfig{
|
||||
ObserverInterval: 10,
|
||||
BackoffSeconds: 2,
|
||||
BackoffCount: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
20
internal/config/dns.go
Normal file
20
internal/config/dns.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package config
|
||||
|
||||
type DnsConfig struct {
|
||||
UseSystemd bool `toml:"use_systemd"`
|
||||
baseRoleConfig
|
||||
}
|
||||
|
||||
func (c DnsConfig) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DnsConfig) Merge(other DnsConfig) {
|
||||
if other.set {
|
||||
c.set = other.set
|
||||
}
|
||||
|
||||
if other.UseSystemd {
|
||||
c.UseSystemd = other.UseSystemd
|
||||
}
|
||||
}
|
||||
70
internal/config/host.go
Normal file
70
internal/config/host.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type HostConfig struct {
|
||||
Domain string `toml:"domain"`
|
||||
IpAddress string `toml:"ip"`
|
||||
LocalAddress string `toml:"local_address"`
|
||||
InternalEntrypoint string `toml:"internal_entrypoint"`
|
||||
ExternalEntrypoint string `toml:"external_entrypoint"`
|
||||
baseRoleConfig
|
||||
}
|
||||
|
||||
func (c HostConfig) Validate() error {
|
||||
if c.Domain == "" {
|
||||
return errors.New("missing domain")
|
||||
}
|
||||
|
||||
if c.IpAddress == "" {
|
||||
return errors.New("missing ip")
|
||||
}
|
||||
|
||||
if net.ParseIP(c.IpAddress) == nil {
|
||||
return fmt.Errorf("invalid ip: %q", c.IpAddress)
|
||||
}
|
||||
|
||||
if c.LocalAddress == "" {
|
||||
return errors.New("missing local address")
|
||||
}
|
||||
|
||||
if c.InternalEntrypoint == "" {
|
||||
return errors.New("missing internal entrypoint")
|
||||
}
|
||||
|
||||
if c.ExternalEntrypoint == "" {
|
||||
return errors.New("missing external entrypoint")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *HostConfig) Merge(other HostConfig) {
|
||||
if other.set {
|
||||
c.set = other.set
|
||||
}
|
||||
|
||||
if other.Domain != "" {
|
||||
c.Domain = other.Domain
|
||||
}
|
||||
|
||||
if other.IpAddress != "" {
|
||||
c.IpAddress = other.IpAddress
|
||||
}
|
||||
|
||||
if other.LocalAddress != "" {
|
||||
c.LocalAddress = other.LocalAddress
|
||||
}
|
||||
|
||||
if other.InternalEntrypoint != "" {
|
||||
c.InternalEntrypoint = other.InternalEntrypoint
|
||||
}
|
||||
|
||||
if other.ExternalEntrypoint != "" {
|
||||
c.ExternalEntrypoint = other.ExternalEntrypoint
|
||||
}
|
||||
}
|
||||
47
internal/config/master.go
Normal file
47
internal/config/master.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package config
|
||||
|
||||
import "errors"
|
||||
|
||||
type MasterConfig struct {
|
||||
ObserverInterval int `toml:"observer_interval"`
|
||||
|
||||
BackoffSeconds int `toml:"backoff_seconds"`
|
||||
BackoffCount int `toml:"backoff_count"`
|
||||
|
||||
baseRoleConfig
|
||||
}
|
||||
|
||||
func (c MasterConfig) Validate() error {
|
||||
if c.ObserverInterval < 1 {
|
||||
return errors.New("invalid observer_interval")
|
||||
}
|
||||
|
||||
if c.BackoffSeconds < 1 {
|
||||
return errors.New("invalid backoff_seconds")
|
||||
}
|
||||
|
||||
if c.BackoffCount < 1 {
|
||||
return errors.New("invalid backoff_count")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MasterConfig) Merge(other MasterConfig) {
|
||||
if other.set {
|
||||
c.set = true
|
||||
}
|
||||
|
||||
if other.ObserverInterval != 0 {
|
||||
c.ObserverInterval = other.ObserverInterval
|
||||
}
|
||||
|
||||
if other.BackoffSeconds != 0 {
|
||||
c.BackoffSeconds = other.BackoffSeconds
|
||||
}
|
||||
|
||||
if other.BackoffCount != 0 {
|
||||
c.BackoffCount = other.BackoffCount
|
||||
}
|
||||
}
|
||||
|
||||
85
internal/config/node.go
Normal file
85
internal/config/node.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
LogLevelDebug LogLevel = "DEBUG"
|
||||
LogLevelInfo LogLevel = "INFO"
|
||||
LogLevelWarn LogLevel = "WARN"
|
||||
LogLevelError LogLevel = "ERROR"
|
||||
)
|
||||
|
||||
func (l *LogLevel) UnmarshalText(data []byte) error {
|
||||
raw := strings.ToUpper(string(data))
|
||||
|
||||
switch LogLevel(raw) {
|
||||
case LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError:
|
||||
*l = LogLevel(raw)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid log level: %q", data)
|
||||
}
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
Hostname string `toml:"hostname"`
|
||||
Endpoint string `toml:"endpoint"`
|
||||
KeepaliveInterval int `toml:"keepalive_interval"`
|
||||
LogLevel LogLevel `toml:"log_level"`
|
||||
|
||||
BootstrapMaster string `toml:"bootstrap_master"`
|
||||
ListenOn string `toml:"listen_on"`
|
||||
Port int `toml:"port"`
|
||||
}
|
||||
|
||||
func (c NodeConfig) Validate() error {
|
||||
if c.Hostname == "" {
|
||||
return errors.New("missing hostname")
|
||||
}
|
||||
|
||||
if c.Endpoint == "" {
|
||||
return errors.New("missing endpoint")
|
||||
}
|
||||
|
||||
if c.KeepaliveInterval < 1 && c.KeepaliveInterval != -1 {
|
||||
return errors.New("invalid keepalive_interval")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NodeConfig) Merge(other NodeConfig) {
|
||||
if other.Hostname != "" {
|
||||
c.Hostname = other.Hostname
|
||||
}
|
||||
|
||||
if other.Endpoint != "" {
|
||||
c.Endpoint = other.Endpoint
|
||||
}
|
||||
|
||||
if other.BootstrapMaster != "" {
|
||||
c.BootstrapMaster = other.BootstrapMaster
|
||||
}
|
||||
|
||||
if other.ListenOn != "" {
|
||||
c.ListenOn = other.ListenOn
|
||||
}
|
||||
|
||||
if other.Port != 0 {
|
||||
c.Port = other.Port
|
||||
}
|
||||
|
||||
if other.KeepaliveInterval != 0 {
|
||||
c.KeepaliveInterval = other.KeepaliveInterval
|
||||
}
|
||||
|
||||
if other.LogLevel != "" {
|
||||
c.LogLevel = other.LogLevel
|
||||
}
|
||||
}
|
||||
5
internal/config/role.go
Normal file
5
internal/config/role.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package config
|
||||
|
||||
type baseRoleConfig struct {
|
||||
set bool
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue