package config import ( "fmt" "os" "sync" "freeleaps.com/gitea-webhook-ambassador/internal/logger" "github.com/go-playground/validator/v10" "gopkg.in/yaml.v2" ) // Configuration holds application configuration type Configuration struct { Server struct { Port int `yaml:"port" validate:"required,gt=0"` WebhookPath string `yaml:"webhookPath" validate:"required"` SecretHeader string `yaml:"secretHeader" default:"Authorization"` SecretKey string `yaml:"secretKey"` } `yaml:"server"` Jenkins struct { URL string `yaml:"url" validate:"required,url"` Username string `yaml:"username"` Token string `yaml:"token"` Timeout int `yaml:"timeout" default:"30"` } `yaml:"jenkins"` Database struct { Path string `yaml:"path" validate:"required"` // Path to SQLite database file } `yaml:"database"` Logging struct { Level string `yaml:"level" default:"info" validate:"oneof=debug info warn error"` Format string `yaml:"format" default:"text" validate:"oneof=text json"` File string `yaml:"file"` } `yaml:"logging"` Worker struct { PoolSize int `yaml:"poolSize" default:"10" validate:"gt=0"` QueueSize int `yaml:"queueSize" default:"100" validate:"gt=0"` MaxRetries int `yaml:"maxRetries" default:"3" validate:"gte=0"` RetryBackoff int `yaml:"retryBackoff" default:"1" validate:"gt=0"` } `yaml:"worker"` EventCleanup struct { Interval int `yaml:"interval" default:"3600"` ExpireAfter int `yaml:"expireAfter" default:"7200"` } `yaml:"eventCleanup"` } // ProjectConfig represents the configuration for a specific repository type ProjectConfig struct { DefaultJob string `yaml:"defaultJob"` BranchJobs map[string]string `yaml:"branchJobs,omitempty"` BranchPatterns []BranchPattern `yaml:"branchPatterns,omitempty"` } // BranchPattern defines a pattern-based branch to job mapping type BranchPattern struct { Pattern string `yaml:"pattern"` Job string `yaml:"job"` } var ( config Configuration configMutex sync.RWMutex validate = validator.New() ) // Load reads and parses the configuration file func Load(file string) error { logger.Debug("Loading configuration from file: %s", file) f, err := os.Open(file) if err != nil { return fmt.Errorf("cannot open config file: %v", err) } defer f.Close() var newConfig Configuration decoder := yaml.NewDecoder(f) if err := decoder.Decode(&newConfig); err != nil { return fmt.Errorf("cannot decode config: %v", err) } setDefaults(&newConfig) if err := validate.Struct(newConfig); err != nil { return fmt.Errorf("invalid configuration: %v", err) } logger.Debug("Configuration loaded successfully - Server.SecretKey length: %d", len(newConfig.Server.SecretKey)) configMutex.Lock() config = newConfig configMutex.Unlock() return nil } // Get returns a copy of the current configuration func Get() Configuration { configMutex.RLock() defer configMutex.RUnlock() return config } // Update atomically updates the configuration func Update(newConfig Configuration) { configMutex.Lock() config = newConfig configMutex.Unlock() } func setDefaults(config *Configuration) { if config.Server.SecretHeader == "" { config.Server.SecretHeader = "X-Gitea-Signature" } if config.Jenkins.Timeout == 0 { config.Jenkins.Timeout = 30 } if config.Worker.PoolSize == 0 { config.Worker.PoolSize = 10 } if config.Worker.QueueSize == 0 { config.Worker.QueueSize = 100 } if config.Worker.MaxRetries == 0 { config.Worker.MaxRetries = 3 } if config.Worker.RetryBackoff == 0 { config.Worker.RetryBackoff = 1 } if config.EventCleanup.Interval == 0 { config.EventCleanup.Interval = 3600 } if config.EventCleanup.ExpireAfter == 0 { config.EventCleanup.ExpireAfter = 7200 } if config.Logging.Level == "" { config.Logging.Level = "info" } if config.Logging.Format == "" { config.Logging.Format = "text" } }