203 lines
5.7 KiB
Go
203 lines
5.7 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/wild-cloud/wild-central/internal/storage"
|
|
)
|
|
|
|
// YQ provides a wrapper around the yq command-line tool
|
|
type YQ struct {
|
|
yqPath string
|
|
}
|
|
|
|
// NewYQ creates a new YQ wrapper
|
|
func NewYQ() *YQ {
|
|
path, err := exec.LookPath("yq")
|
|
if err != nil {
|
|
path = "yq"
|
|
}
|
|
return &YQ{yqPath: path}
|
|
}
|
|
|
|
// Get retrieves a value from a YAML file using a yq expression
|
|
func (y *YQ) Get(filePath, expression string) (string, error) {
|
|
cmd := exec.Command(y.yqPath, expression, filePath)
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return "", fmt.Errorf("yq get failed: %w, stderr: %s", err, stderr.String())
|
|
}
|
|
return strings.TrimSpace(stdout.String()), nil
|
|
}
|
|
|
|
// Set sets a value in a YAML file using a yq expression
|
|
func (y *YQ) Set(filePath, expression, value string) error {
|
|
if !strings.HasPrefix(expression, ".") {
|
|
expression = "." + expression
|
|
}
|
|
quotedValue := fmt.Sprintf(`"%s"`, strings.ReplaceAll(value, `"`, `\"`))
|
|
setExpr := fmt.Sprintf("%s = %s", expression, quotedValue)
|
|
cmd := exec.Command(y.yqPath, "-i", setExpr, filePath)
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("yq set failed: %w, stderr: %s", err, stderr.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a key from a YAML file
|
|
func (y *YQ) Delete(filePath, expression string) error {
|
|
delExpr := fmt.Sprintf("del(%s)", expression)
|
|
cmd := exec.Command(y.yqPath, "-i", delExpr, filePath)
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("yq delete failed: %w, stderr: %s", err, stderr.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Validate checks if a YAML file is valid
|
|
func (y *YQ) Validate(filePath string) error {
|
|
cmd := exec.Command(y.yqPath, "eval", ".", filePath)
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("yaml validation failed: %w, stderr: %s", err, stderr.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Manager handles configuration file operations with idempotency
|
|
type Manager struct {
|
|
yq *YQ
|
|
}
|
|
|
|
// NewManager creates a new config manager
|
|
func NewManager() *Manager {
|
|
return &Manager{
|
|
yq: NewYQ(),
|
|
}
|
|
}
|
|
|
|
// EnsureInstanceConfig ensures an instance config file exists with proper structure
|
|
func (m *Manager) EnsureInstanceConfig(name string, dataDir string) error {
|
|
configPath := filepath.Join(dataDir, "instances", name, "config", "instance.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
|
|
}
|
|
|
|
// Ensure config directory exists
|
|
if err := storage.EnsureDir(filepath.Join(dataDir, "instances", name, "config"), 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
initialConfig := &InstanceConfig{}
|
|
initialConfig.Cluster.Name = name
|
|
initialConfig.Cluster.Nodes.Active = make(map[string]NodeConfig)
|
|
initialConfig.Apps = make(map[string]any)
|
|
return SaveCloudConfig(initialConfig, configPath)
|
|
}
|
|
|
|
// 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", "instance.yaml")
|
|
}
|
|
|
|
// GetInstanceSecretsPath returns the path to an instance's secrets file
|
|
func GetInstanceSecretsPath(dataDir, instanceName string) string {
|
|
return filepath.Join(dataDir, "instances", instanceName, "config", "secrets.yaml")
|
|
}
|
|
|
|
// GetInstancePath returns the path to an instance directory
|
|
func GetInstancePath(dataDir, instanceName string) string {
|
|
return filepath.Join(dataDir, "instances", instanceName)
|
|
}
|