cdf21682d6
AES-256-GCM encryption for credential storage, YAML seed config parser with validation, and transactional import into SQLite. Credentials (registry tokens, NPM password) encrypted before storage.
113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
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
|
|
}
|