package config import ( "fmt" "os" "gopkg.in/yaml.v3" ) // SeedConfig represents the top-level YAML seed configuration. type SeedConfig struct { Global GlobalConfig `yaml:"global"` Registries map[string]RegistryDef `yaml:"registries"` Projects map[string]ProjectDef `yaml:"projects"` } // GlobalConfig holds domain-wide settings from the seed file. type GlobalConfig struct { Domain string `yaml:"domain"` ServerIP string `yaml:"server_ip"` Network string `yaml:"network"` SubdomainPattern string `yaml:"subdomain_pattern"` NotificationURL string `yaml:"notification_url"` Npm NpmConfig `yaml:"npm"` } // NpmConfig holds Nginx Proxy Manager connection details. type NpmConfig struct { URL string `yaml:"url"` Email string `yaml:"email"` Password string `yaml:"password"` } // RegistryDef defines a container registry from the seed file. type RegistryDef struct { URL string `yaml:"url"` Type string `yaml:"type"` Token string `yaml:"token"` } // ProjectDef defines a project from the seed file. type ProjectDef struct { Registry string `yaml:"registry"` Image string `yaml:"image"` Port int `yaml:"port"` Healthcheck string `yaml:"healthcheck"` Env map[string]string `yaml:"env"` Volumes map[string]string `yaml:"volumes"` Stages map[string]StageDef `yaml:"stages"` } // StageDef defines a deployment stage from the seed file. type StageDef struct { TagPattern string `yaml:"tag_pattern"` AutoDeploy bool `yaml:"auto_deploy"` MaxInstances int `yaml:"max_instances"` Confirm bool `yaml:"confirm"` PromoteFrom string `yaml:"promote_from"` Subdomain string `yaml:"subdomain"` } // LoadSeedFile reads and parses the YAML seed config from the given path. func LoadSeedFile(path string) (SeedConfig, error) { data, err := os.ReadFile(path) if err != nil { return SeedConfig{}, fmt.Errorf("read seed file: %w", err) } return ParseSeed(data) } // ParseSeed parses raw YAML bytes into a SeedConfig. func ParseSeed(data []byte) (SeedConfig, error) { var cfg SeedConfig if err := yaml.Unmarshal(data, &cfg); err != nil { return SeedConfig{}, fmt.Errorf("parse yaml: %w", err) } if err := validate(cfg); err != nil { return SeedConfig{}, fmt.Errorf("validate seed config: %w", err) } return cfg, nil } // validate checks that required fields are present in the seed config. func validate(cfg SeedConfig) error { if cfg.Global.Domain == "" { return fmt.Errorf("global.domain is required") } for name, proj := range cfg.Projects { if proj.Image == "" { return fmt.Errorf("project %q: image is required", name) } if proj.Registry != "" { if _, ok := cfg.Registries[proj.Registry]; !ok { return fmt.Errorf("project %q: references unknown registry %q", name, proj.Registry) } } for stageName, stage := range proj.Stages { if stage.TagPattern == "" { return fmt.Errorf("project %q stage %q: tag_pattern is required", name, stageName) } if stage.MaxInstances < 0 { return fmt.Errorf("project %q stage %q: max_instances must be >= 0", name, stageName) } } } return nil }