Initial commit.
This commit is contained in:
168
internal/config/config.go
Normal file
168
internal/config/config.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// GlobalConfig represents the main configuration structure
|
||||
type GlobalConfig struct {
|
||||
Wildcloud struct {
|
||||
Repository string `yaml:"repository" json:"repository"`
|
||||
CurrentPhase string `yaml:"currentPhase" json:"currentPhase"`
|
||||
CompletedPhases []string `yaml:"completedPhases" json:"completedPhases"`
|
||||
} `yaml:"wildcloud" json:"wildcloud"`
|
||||
Server struct {
|
||||
Port int `yaml:"port" json:"port"`
|
||||
Host string `yaml:"host" json:"host"`
|
||||
} `yaml:"server" json:"server"`
|
||||
Operator struct {
|
||||
Email string `yaml:"email" json:"email"`
|
||||
} `yaml:"operator" json:"operator"`
|
||||
Cloud struct {
|
||||
DNS struct {
|
||||
IP string `yaml:"ip" json:"ip"`
|
||||
ExternalResolver string `yaml:"externalResolver" json:"externalResolver"`
|
||||
} `yaml:"dns" json:"dns"`
|
||||
Router struct {
|
||||
IP string `yaml:"ip" json:"ip"`
|
||||
DynamicDns string `yaml:"dynamicDns" json:"dynamicDns"`
|
||||
} `yaml:"router" json:"router"`
|
||||
Dnsmasq struct {
|
||||
Interface string `yaml:"interface" json:"interface"`
|
||||
} `yaml:"dnsmasq" json:"dnsmasq"`
|
||||
} `yaml:"cloud" json:"cloud"`
|
||||
Cluster struct {
|
||||
EndpointIP string `yaml:"endpointIp" json:"endpointIp"`
|
||||
Nodes struct {
|
||||
Talos struct {
|
||||
Version string `yaml:"version" json:"version"`
|
||||
} `yaml:"talos" json:"talos"`
|
||||
} `yaml:"nodes" json:"nodes"`
|
||||
} `yaml:"cluster" json:"cluster"`
|
||||
}
|
||||
|
||||
// LoadGlobalConfig loads configuration from the specified path
|
||||
func LoadGlobalConfig(configPath string) (*GlobalConfig, error) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading config file %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
config := &GlobalConfig{}
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return nil, fmt.Errorf("parsing config file: %w", err)
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
if config.Server.Port == 0 {
|
||||
config.Server.Port = 5055
|
||||
}
|
||||
if config.Server.Host == "" {
|
||||
config.Server.Host = "0.0.0.0"
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveGlobalConfig saves the configuration to the specified path
|
||||
func SaveGlobalConfig(config *GlobalConfig, 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)
|
||||
}
|
||||
|
||||
// IsEmpty checks if the configuration is empty or uninitialized
|
||||
func (c *GlobalConfig) IsEmpty() bool {
|
||||
if c == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if any essential fields are empty
|
||||
return c.Cloud.DNS.IP == "" || c.Cluster.Nodes.Talos.Version == ""
|
||||
}
|
||||
|
||||
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 {
|
||||
BaseDomain string `yaml:"baseDomain" json:"baseDomain"`
|
||||
Domain string `yaml:"domain" json:"domain"`
|
||||
InternalDomain string `yaml:"internalDomain" json:"internalDomain"`
|
||||
Backup struct {
|
||||
Root string `yaml:"root" json:"root"`
|
||||
} `yaml:"backup" json:"backup"`
|
||||
DHCPRange string `yaml:"dhcpRange" json:"dhcpRange"`
|
||||
NFS struct {
|
||||
Host string `yaml:"host" json:"host"`
|
||||
MediaPath string `yaml:"mediaPath" json:"mediaPath"`
|
||||
} `yaml:"nfs" json:"nfs"`
|
||||
Cluster struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
LoadBalancerIp string `yaml:"loadBalancerIp" json:"loadBalancerIp"`
|
||||
IpAddressPool string `yaml:"ipAddressPool" json:"ipAddressPool"`
|
||||
CertManager struct {
|
||||
Cloudflare struct {
|
||||
Domain string `yaml:"domain" json:"domain"`
|
||||
ZoneID string `yaml:"zoneID" json:"zoneID"`
|
||||
} `yaml:"cloudflare" json:"cloudflare"`
|
||||
} `yaml:"certManager" json:"certManager"`
|
||||
ExternalDns struct {
|
||||
OwnerId string `yaml:"ownerId" json:"ownerId"`
|
||||
} `yaml:"externalDns" json:"externalDns"`
|
||||
HostnamePrefix string `yaml:"hostnamePrefix" json:"hostnamePrefix"`
|
||||
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"`
|
||||
ActiveNodes []map[string]NodeConfig `yaml:"activeNodes" json:"activeNodes"`
|
||||
}
|
||||
} `yaml:"cluster" json:"cluster"`
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
167
internal/config/manager.go
Normal file
167
internal/config/manager.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/storage"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
||||
)
|
||||
|
||||
// Manager handles configuration file operations with idempotency
|
||||
type Manager struct {
|
||||
yq *tools.YQ
|
||||
}
|
||||
|
||||
// NewManager creates a new config manager
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
yq: tools.NewYQ(),
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureInstanceConfig ensures an instance config file exists with proper structure
|
||||
func (m *Manager) EnsureInstanceConfig(instancePath string) error {
|
||||
configPath := filepath.Join(instancePath, "config.yaml")
|
||||
|
||||
// Check if config already exists
|
||||
if storage.FileExists(configPath) {
|
||||
// Validate existing config
|
||||
if err := m.yq.Validate(configPath); err != nil {
|
||||
return fmt.Errorf("invalid config file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create minimal config structure
|
||||
initialConfig := `# Wild Cloud Instance Configuration
|
||||
baseDomain: ""
|
||||
domain: ""
|
||||
internalDomain: ""
|
||||
dhcpRange: ""
|
||||
backup:
|
||||
root: ""
|
||||
nfs:
|
||||
host: ""
|
||||
mediaPath: ""
|
||||
cluster:
|
||||
name: ""
|
||||
loadBalancerIp: ""
|
||||
ipAddressPool: ""
|
||||
hostnamePrefix: ""
|
||||
certManager:
|
||||
cloudflare:
|
||||
domain: ""
|
||||
zoneID: ""
|
||||
externalDns:
|
||||
ownerId: ""
|
||||
nodes:
|
||||
talos:
|
||||
version: ""
|
||||
schematicId: ""
|
||||
control:
|
||||
vip: ""
|
||||
activeNodes: []
|
||||
`
|
||||
|
||||
// Ensure instance directory exists
|
||||
if err := storage.EnsureDir(instancePath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write config with proper permissions
|
||||
if err := storage.WriteFile(configPath, []byte(initialConfig), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfigValue retrieves a value from a config file
|
||||
func (m *Manager) GetConfigValue(configPath, key string) (string, error) {
|
||||
if !storage.FileExists(configPath) {
|
||||
return "", fmt.Errorf("config file not found: %s", configPath)
|
||||
}
|
||||
|
||||
value, err := m.yq.Get(configPath, fmt.Sprintf(".%s", key))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting config value %s: %w", key, err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// SetConfigValue sets a value in a config file
|
||||
func (m *Manager) SetConfigValue(configPath, key, value string) error {
|
||||
if !storage.FileExists(configPath) {
|
||||
return fmt.Errorf("config file not found: %s", configPath)
|
||||
}
|
||||
|
||||
// Acquire lock before modifying
|
||||
lockPath := configPath + ".lock"
|
||||
return storage.WithLock(lockPath, func() error {
|
||||
return m.yq.Set(configPath, fmt.Sprintf(".%s", key), value)
|
||||
})
|
||||
}
|
||||
|
||||
// EnsureConfigValue sets a value only if it's not already set (idempotent)
|
||||
func (m *Manager) EnsureConfigValue(configPath, key, value string) error {
|
||||
if !storage.FileExists(configPath) {
|
||||
return fmt.Errorf("config file not found: %s", configPath)
|
||||
}
|
||||
|
||||
// Check if value already set
|
||||
currentValue, err := m.GetConfigValue(configPath, key)
|
||||
if err == nil && currentValue != "" && currentValue != "null" {
|
||||
// Value already set, skip
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the value
|
||||
return m.SetConfigValue(configPath, key, value)
|
||||
}
|
||||
|
||||
// ValidateConfig validates a config file
|
||||
func (m *Manager) ValidateConfig(configPath string) error {
|
||||
if !storage.FileExists(configPath) {
|
||||
return fmt.Errorf("config file not found: %s", configPath)
|
||||
}
|
||||
|
||||
return m.yq.Validate(configPath)
|
||||
}
|
||||
|
||||
// CopyConfig copies a config file to a new location
|
||||
func (m *Manager) CopyConfig(srcPath, dstPath string) error {
|
||||
if !storage.FileExists(srcPath) {
|
||||
return fmt.Errorf("source config file not found: %s", srcPath)
|
||||
}
|
||||
|
||||
// Read source
|
||||
content, err := storage.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
if err := storage.EnsureDir(filepath.Dir(dstPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write destination
|
||||
return storage.WriteFile(dstPath, content, 0644)
|
||||
}
|
||||
|
||||
// GetInstanceConfigPath returns the path to an instance's config file
|
||||
func GetInstanceConfigPath(dataDir, instanceName string) string {
|
||||
return filepath.Join(dataDir, "instances", instanceName, "config.yaml")
|
||||
}
|
||||
|
||||
// GetInstanceSecretsPath returns the path to an instance's secrets file
|
||||
func GetInstanceSecretsPath(dataDir, instanceName string) string {
|
||||
return filepath.Join(dataDir, "instances", instanceName, "secrets.yaml")
|
||||
}
|
||||
|
||||
// GetInstancePath returns the path to an instance directory
|
||||
func GetInstancePath(dataDir, instanceName string) string {
|
||||
return filepath.Join(dataDir, "instances", instanceName)
|
||||
}
|
||||
Reference in New Issue
Block a user