163 lines
4.9 KiB
Go
163 lines
4.9 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"github.com/tech/sendico/pkg/mlogger"
|
|
"go.uber.org/zap"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type service struct {
|
|
logger mlogger.Logger
|
|
}
|
|
|
|
// New creates a configuration loader.
|
|
func New(logger mlogger.Logger) Loader {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &service{logger: logger.Named("config")}
|
|
}
|
|
|
|
func (s *service) Load(path string) (*Config, error) {
|
|
if strings.TrimSpace(path) == "" {
|
|
return nil, merrors.InvalidArgument("config path is required", "path")
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
s.logger.Error("Failed to read config file", zap.String("path", path), zap.Error(err))
|
|
return nil, merrors.InternalWrap(err, "failed to read callbacks config")
|
|
}
|
|
|
|
cfg := &Config{}
|
|
if err := yaml.Unmarshal(data, cfg); err != nil {
|
|
s.logger.Error("Failed to parse config yaml", zap.String("path", path), zap.Error(err))
|
|
return nil, merrors.InternalWrap(err, "failed to parse callbacks config")
|
|
}
|
|
|
|
s.applyDefaults(cfg)
|
|
if err := s.validate(cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (s *service) applyDefaults(cfg *Config) {
|
|
if cfg.Runtime == nil {
|
|
cfg.Runtime = &RuntimeConfig{ShutdownTimeoutSeconds: defaultShutdownTimeoutSeconds}
|
|
}
|
|
|
|
if cfg.Metrics == nil {
|
|
cfg.Metrics = &MetricsConfig{Address: defaultMetricsAddress}
|
|
} else if strings.TrimSpace(cfg.Metrics.Address) == "" {
|
|
cfg.Metrics.Address = defaultMetricsAddress
|
|
}
|
|
|
|
if strings.TrimSpace(cfg.Ingest.Stream) == "" {
|
|
cfg.Ingest.Stream = defaultIngestStream
|
|
}
|
|
if strings.TrimSpace(cfg.Ingest.Subject) == "" {
|
|
cfg.Ingest.Subject = defaultIngestSubject
|
|
}
|
|
if strings.TrimSpace(cfg.Ingest.Durable) == "" {
|
|
cfg.Ingest.Durable = defaultIngestDurable
|
|
}
|
|
if cfg.Ingest.BatchSize <= 0 {
|
|
cfg.Ingest.BatchSize = defaultIngestBatchSize
|
|
}
|
|
if cfg.Ingest.FetchTimeoutMS <= 0 {
|
|
cfg.Ingest.FetchTimeoutMS = defaultIngestFetchTimeoutMS
|
|
}
|
|
if cfg.Ingest.IdleSleepMS <= 0 {
|
|
cfg.Ingest.IdleSleepMS = defaultIngestIdleSleepMS
|
|
}
|
|
|
|
if cfg.Delivery.WorkerConcurrency <= 0 {
|
|
cfg.Delivery.WorkerConcurrency = defaultWorkerConcurrency
|
|
}
|
|
if cfg.Delivery.WorkerPollMS <= 0 {
|
|
cfg.Delivery.WorkerPollMS = defaultWorkerPollIntervalMS
|
|
}
|
|
if cfg.Delivery.LockTTLSeconds <= 0 {
|
|
cfg.Delivery.LockTTLSeconds = defaultLockTTLSeconds
|
|
}
|
|
if cfg.Delivery.RequestTimeoutMS <= 0 {
|
|
cfg.Delivery.RequestTimeoutMS = defaultRequestTimeoutMS
|
|
}
|
|
if cfg.Delivery.MaxAttempts <= 0 {
|
|
cfg.Delivery.MaxAttempts = defaultMaxAttempts
|
|
}
|
|
if cfg.Delivery.MinDelayMS <= 0 {
|
|
cfg.Delivery.MinDelayMS = defaultMinDelayMS
|
|
}
|
|
if cfg.Delivery.MaxDelayMS <= 0 {
|
|
cfg.Delivery.MaxDelayMS = defaultMaxDelayMS
|
|
}
|
|
if cfg.Delivery.JitterRatio <= 0 {
|
|
cfg.Delivery.JitterRatio = defaultJitterRatio
|
|
}
|
|
if cfg.Delivery.JitterRatio > 1 {
|
|
cfg.Delivery.JitterRatio = 1
|
|
}
|
|
|
|
if cfg.Security.DNSResolveTimeout <= 0 {
|
|
cfg.Security.DNSResolveTimeout = defaultDNSResolveTimeoutMS
|
|
}
|
|
if len(cfg.Security.AllowedPorts) == 0 {
|
|
cfg.Security.AllowedPorts = []int{443}
|
|
}
|
|
if !cfg.Security.RequireHTTPS {
|
|
cfg.Security.RequireHTTPS = true
|
|
}
|
|
|
|
if cfg.Secrets.Static == nil {
|
|
cfg.Secrets.Static = map[string]string{}
|
|
}
|
|
if strings.TrimSpace(cfg.Secrets.Vault.DefaultField) == "" {
|
|
cfg.Secrets.Vault.DefaultField = defaultSecretsVaultField
|
|
}
|
|
}
|
|
|
|
func (s *service) validate(cfg *Config) error {
|
|
if cfg.Database == nil {
|
|
return merrors.InvalidArgument("database configuration is required", "database")
|
|
}
|
|
if cfg.Messaging == nil {
|
|
return merrors.InvalidArgument("messaging configuration is required", "messaging")
|
|
}
|
|
if strings.TrimSpace(string(cfg.Messaging.Driver)) == "" {
|
|
return merrors.InvalidArgument("messaging.driver is required", "messaging.driver")
|
|
}
|
|
if cfg.Delivery.MinDelay() > cfg.Delivery.MaxDelay() {
|
|
return merrors.InvalidArgument("delivery min delay must be <= max delay", "delivery.min_delay_ms", "delivery.max_delay_ms")
|
|
}
|
|
if cfg.Delivery.MaxAttempts < 1 {
|
|
return merrors.InvalidArgument("delivery.max_attempts must be > 0", "delivery.max_attempts")
|
|
}
|
|
if cfg.Ingest.BatchSize < 1 {
|
|
return merrors.InvalidArgument("ingest.batch_size must be > 0", "ingest.batch_size")
|
|
}
|
|
vaultAddress := strings.TrimSpace(cfg.Secrets.Vault.Address)
|
|
vaultTokenEnv := strings.TrimSpace(cfg.Secrets.Vault.TokenEnv)
|
|
vaultMountPath := strings.TrimSpace(cfg.Secrets.Vault.MountPath)
|
|
hasVault := vaultAddress != "" || vaultTokenEnv != "" || vaultMountPath != ""
|
|
if hasVault {
|
|
if vaultAddress == "" {
|
|
return merrors.InvalidArgument("secrets.vault.address is required when vault settings are configured", "secrets.vault.address")
|
|
}
|
|
if vaultTokenEnv == "" {
|
|
return merrors.InvalidArgument("secrets.vault.token_env is required when vault settings are configured", "secrets.vault.token_env")
|
|
}
|
|
if vaultMountPath == "" {
|
|
return merrors.InvalidArgument("secrets.vault.mount_path is required when vault settings are configured", "secrets.vault.mount_path")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|