Replace InstanceConfig with DNSEntry for dnsmasq config generation
InstanceConfig was a ~50-field struct designed for Wild Cloud k8s instances,
but only 3 fields were ever read by dnsmasq (the sole consumer). The
reconciliation bridge constructed fake InstanceConfig objects from registered
domains just to satisfy this interface.
Replace with DNSEntry{Domain, IP} — a 2-field struct purpose-built for
dnsmasq. All domains get local=/ directives to prevent AAAA queries from
leaking to upstream DNS (Happy Eyeballs / RFC 8305 latency on LAN).
Removed dead code: InstanceConfig, NodeConfig, LoadCloudConfig,
SaveCloudConfig, DeepMerge, LoadMergedInstanceConfig, EnsureInstanceConfig,
instance path helpers, instanceLoadBalancerIP, GenerateInstanceConfig,
and all modular per-instance dnsmasq methods.
This commit is contained in:
@@ -3,7 +3,6 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -158,168 +157,3 @@ func (c *State) IsEmpty() bool {
|
||||
return c.Cloud.Central.Domain == "" && c.Operator.Email == ""
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
Role string `yaml:"role" json:"role"`
|
||||
Interface string `yaml:"interface" json:"interface"`
|
||||
Disk string `yaml:"disk" json:"disk"`
|
||||
CurrentIp string `yaml:"currentIp" json:"currentIp"`
|
||||
}
|
||||
|
||||
type InstanceConfig struct {
|
||||
Operator struct {
|
||||
Email string `yaml:"email" json:"email"`
|
||||
} `yaml:"operator" json:"operator"`
|
||||
Cloud struct {
|
||||
BaseDomain string `yaml:"baseDomain" json:"baseDomain"`
|
||||
Domain string `yaml:"domain" json:"domain"`
|
||||
InternalDomain string `yaml:"internalDomain" json:"internalDomain"`
|
||||
DHCPRange string `yaml:"dhcpRange" json:"dhcpRange"`
|
||||
NFS struct {
|
||||
Host string `yaml:"host" json:"host"`
|
||||
MediaPath string `yaml:"mediaPath" json:"mediaPath"`
|
||||
StorageCapacity string `yaml:"storageCapacity" json:"storageCapacity"`
|
||||
} `yaml:"nfs" json:"nfs"`
|
||||
DockerRegistryHost string `yaml:"dockerRegistryHost" json:"dockerRegistryHost"`
|
||||
} `yaml:"cloud" json:"cloud"`
|
||||
Cluster struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
LoadBalancerIp string `yaml:"loadBalancerIp" json:"loadBalancerIp"`
|
||||
IpAddressPool string `yaml:"ipAddressPool" json:"ipAddressPool"`
|
||||
HostnamePrefix string `yaml:"hostnamePrefix" json:"hostnamePrefix"`
|
||||
CertManager struct {
|
||||
Cloudflare struct {
|
||||
Domain string `yaml:"domain" json:"domain"`
|
||||
} `yaml:"cloudflare" json:"cloudflare"`
|
||||
} `yaml:"certManager" json:"certManager"`
|
||||
ExternalDns struct {
|
||||
OwnerId string `yaml:"ownerId" json:"ownerId"`
|
||||
} `yaml:"externalDns" json:"externalDns"`
|
||||
InternalDns struct {
|
||||
ExternalResolver string `yaml:"externalResolver" json:"externalResolver"`
|
||||
} `yaml:"internalDns" json:"internalDns"`
|
||||
DockerRegistry struct {
|
||||
Storage string `yaml:"storage" json:"storage"`
|
||||
} `yaml:"dockerRegistry" json:"dockerRegistry"`
|
||||
Nodes struct {
|
||||
Talos struct {
|
||||
Version string `yaml:"version" json:"version"`
|
||||
SchematicId string `yaml:"schematicId" json:"schematicId"`
|
||||
} `yaml:"talos" json:"talos"`
|
||||
Control struct {
|
||||
Vip string `yaml:"vip" json:"vip"`
|
||||
} `yaml:"control" json:"control"`
|
||||
Active map[string]NodeConfig `yaml:"active" json:"active"`
|
||||
} `yaml:"nodes" json:"nodes"`
|
||||
} `yaml:"cluster" json:"cluster"`
|
||||
Apps map[string]any `yaml:"apps" json:"apps"`
|
||||
}
|
||||
|
||||
func LoadCloudConfig(configPath string) (*InstanceConfig, error) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading config file %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
config := &InstanceConfig{}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return nil, fmt.Errorf("parsing config file: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func SaveCloudConfig(config *InstanceConfig, configPath string) error {
|
||||
// Ensure the directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
|
||||
return fmt.Errorf("creating config directory: %w", err)
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling config: %w", err)
|
||||
}
|
||||
|
||||
return os.WriteFile(configPath, data, 0644)
|
||||
}
|
||||
|
||||
// DeepMerge recursively merges src into dst, with src values taking precedence.
|
||||
// Nested maps are merged recursively; all other types are overwritten by src.
|
||||
func DeepMerge(dst, src map[string]any) map[string]any {
|
||||
result := make(map[string]any)
|
||||
for k, v := range dst {
|
||||
result[k] = v
|
||||
}
|
||||
for k, v := range src {
|
||||
if srcMap, ok := v.(map[string]any); ok {
|
||||
if dstMap, ok := result[k].(map[string]any); ok {
|
||||
result[k] = DeepMerge(dstMap, srcMap)
|
||||
continue
|
||||
}
|
||||
}
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// LoadMergedInstanceConfig loads the global config as a base and merges the
|
||||
// instance config on top. Returns the merged result as an untyped map suitable
|
||||
// for passing to gomplate as template context.
|
||||
// If the global config is missing, returns the instance config alone.
|
||||
// Assembles apps.* from config/apps/*/config.yaml files.
|
||||
func LoadMergedInstanceConfig(dataDir, instanceName string) (map[string]any, error) {
|
||||
instanceConfigPath := filepath.Join(dataDir, "instances", instanceName, "config", "instance.yaml")
|
||||
globalConfigPath := filepath.Join(dataDir, "state.yaml")
|
||||
|
||||
instanceData, err := os.ReadFile(instanceConfigPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading instance config %s: %w", instanceConfigPath, err)
|
||||
}
|
||||
|
||||
var instanceMap map[string]any
|
||||
if err := yaml.Unmarshal(instanceData, &instanceMap); err != nil {
|
||||
return nil, fmt.Errorf("parsing instance config: %w", err)
|
||||
}
|
||||
if instanceMap == nil {
|
||||
instanceMap = make(map[string]any)
|
||||
}
|
||||
|
||||
// Assemble apps.* map from per-app config files
|
||||
appsConfigDir := filepath.Join(dataDir, "instances", instanceName, "config", "apps")
|
||||
if entries, err := os.ReadDir(appsConfigDir); err == nil {
|
||||
appsMap := make(map[string]any)
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
appName := entry.Name()
|
||||
appConfigPath := filepath.Join(dataDir, "instances", instanceName, "config", "apps", appName, "config.yaml")
|
||||
appData, err := os.ReadFile(appConfigPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var appConfig map[string]any
|
||||
if err := yaml.Unmarshal(appData, &appConfig); err == nil && appConfig != nil {
|
||||
appsMap[appName] = appConfig
|
||||
}
|
||||
}
|
||||
if len(appsMap) > 0 {
|
||||
instanceMap["apps"] = appsMap
|
||||
}
|
||||
}
|
||||
|
||||
globalData, err := os.ReadFile(globalConfigPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
slog.Warn("no global config found, using instance config only", "path", globalConfigPath)
|
||||
return instanceMap, nil
|
||||
}
|
||||
return nil, fmt.Errorf("reading global config %s: %w", globalConfigPath, err)
|
||||
}
|
||||
|
||||
var globalMap map[string]any
|
||||
if err := yaml.Unmarshal(globalData, &globalMap); err != nil {
|
||||
return nil, fmt.Errorf("parsing global config: %w", err)
|
||||
}
|
||||
|
||||
return DeepMerge(globalMap, instanceMap), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user