206 lines
6.0 KiB
Go
206 lines
6.0 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// LoadFromEnv builds Config from environment variables and validates the
|
|
// resulting configuration.
|
|
func LoadFromEnv() (Config, error) {
|
|
cfg := DefaultConfig()
|
|
|
|
var err error
|
|
|
|
cfg.ShutdownTimeout, err = durationEnv(shutdownTimeoutEnvVar, cfg.ShutdownTimeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
cfg.Logging.Level = stringEnv(logLevelEnvVar, cfg.Logging.Level)
|
|
|
|
cfg.InternalHTTP.Addr = stringEnv(internalHTTPAddrEnvVar, cfg.InternalHTTP.Addr)
|
|
cfg.InternalHTTP.ReadHeaderTimeout, err = durationEnv(internalHTTPReadHeaderTimeoutEnvVar, cfg.InternalHTTP.ReadHeaderTimeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.InternalHTTP.ReadTimeout, err = durationEnv(internalHTTPReadTimeoutEnvVar, cfg.InternalHTTP.ReadTimeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.InternalHTTP.IdleTimeout, err = durationEnv(internalHTTPIdleTimeoutEnvVar, cfg.InternalHTTP.IdleTimeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
cfg.Redis.Addr = stringEnv(redisAddrEnvVar, cfg.Redis.Addr)
|
|
cfg.Redis.Username = stringEnv(redisUsernameEnvVar, cfg.Redis.Username)
|
|
cfg.Redis.Password = stringEnv(redisPasswordEnvVar, cfg.Redis.Password)
|
|
cfg.Redis.DB, err = intEnv(redisDBEnvVar, cfg.Redis.DB)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.Redis.TLSEnabled, err = boolEnv(redisTLSEnabledEnvVar, cfg.Redis.TLSEnabled)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.Redis.OperationTimeout, err = durationEnv(redisOperationTimeoutEnvVar, cfg.Redis.OperationTimeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.Redis.CommandStream = stringEnv(redisCommandStreamEnvVar, cfg.Redis.CommandStream)
|
|
cfg.Redis.AttemptScheduleKey = stringEnv(redisAttemptScheduleEnvVar, cfg.Redis.AttemptScheduleKey)
|
|
cfg.Redis.DeadLetterPrefix = stringEnv(redisDeadLetterPrefixEnvVar, cfg.Redis.DeadLetterPrefix)
|
|
|
|
cfg.SMTP.Mode = stringEnv(smtpModeEnvVar, cfg.SMTP.Mode)
|
|
cfg.SMTP.Addr = stringEnv(smtpAddrEnvVar, cfg.SMTP.Addr)
|
|
cfg.SMTP.Username = stringEnv(smtpUsernameEnvVar, cfg.SMTP.Username)
|
|
cfg.SMTP.Password = stringEnv(smtpPasswordEnvVar, cfg.SMTP.Password)
|
|
cfg.SMTP.FromEmail = stringEnv(smtpFromEmailEnvVar, cfg.SMTP.FromEmail)
|
|
cfg.SMTP.FromName = stringEnv(smtpFromNameEnvVar, cfg.SMTP.FromName)
|
|
cfg.SMTP.Timeout, err = durationEnv(smtpTimeoutEnvVar, cfg.SMTP.Timeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.SMTP.InsecureSkipVerify, err = boolEnv(smtpInsecureSkipVerifyEnvVar, cfg.SMTP.InsecureSkipVerify)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
cfg.Templates.Dir = stringEnv(templateDirEnvVar, cfg.Templates.Dir)
|
|
|
|
cfg.AttemptWorkerConcurrency, err = intEnv(attemptWorkerConcurrencyEnvVar, cfg.AttemptWorkerConcurrency)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.StreamBlockTimeout, err = durationEnv(streamBlockTimeoutEnvVar, cfg.StreamBlockTimeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.OperatorRequestTimeout, err = durationEnv(operatorRequestTimeoutEnvVar, cfg.OperatorRequestTimeout)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.IdempotencyTTL, err = durationEnv(idempotencyTTLEnvVar, cfg.IdempotencyTTL)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.DeliveryTTL, err = durationEnv(deliveryTTLEnvVar, cfg.DeliveryTTL)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.AttemptTTL, err = durationEnv(attemptTTLEnvVar, cfg.AttemptTTL)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
cfg.Telemetry.ServiceName = stringEnv(otelServiceNameEnvVar, cfg.Telemetry.ServiceName)
|
|
cfg.Telemetry.TracesExporter = normalizeExporterValue(stringEnv(otelTracesExporterEnvVar, cfg.Telemetry.TracesExporter))
|
|
cfg.Telemetry.MetricsExporter = normalizeExporterValue(stringEnv(otelMetricsExporterEnvVar, cfg.Telemetry.MetricsExporter))
|
|
cfg.Telemetry.TracesProtocol = normalizeProtocolValue(
|
|
os.Getenv(otelExporterOTLPTracesProtocolEnvVar),
|
|
os.Getenv(otelExporterOTLPProtocolEnvVar),
|
|
cfg.Telemetry.TracesProtocol,
|
|
)
|
|
cfg.Telemetry.MetricsProtocol = normalizeProtocolValue(
|
|
os.Getenv(otelExporterOTLPMetricsProtocolEnvVar),
|
|
os.Getenv(otelExporterOTLPProtocolEnvVar),
|
|
cfg.Telemetry.MetricsProtocol,
|
|
)
|
|
cfg.Telemetry.StdoutTracesEnabled, err = boolEnv(otelStdoutTracesEnabledEnvVar, cfg.Telemetry.StdoutTracesEnabled)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
cfg.Telemetry.StdoutMetricsEnabled, err = boolEnv(otelStdoutMetricsEnabledEnvVar, cfg.Telemetry.StdoutMetricsEnabled)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
if err := validateSlogLevel(cfg.Logging.Level); err != nil {
|
|
return Config{}, fmt.Errorf("%s: %w", logLevelEnvVar, err)
|
|
}
|
|
if err := cfg.Validate(); err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func stringEnv(name string, fallback string) string {
|
|
value, ok := os.LookupEnv(name)
|
|
if !ok {
|
|
return fallback
|
|
}
|
|
|
|
return strings.TrimSpace(value)
|
|
}
|
|
|
|
func durationEnv(name string, fallback time.Duration) (time.Duration, error) {
|
|
value, ok := os.LookupEnv(name)
|
|
if !ok {
|
|
return fallback, nil
|
|
}
|
|
|
|
parsed, err := time.ParseDuration(strings.TrimSpace(value))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%s: parse duration: %w", name, err)
|
|
}
|
|
|
|
return parsed, nil
|
|
}
|
|
|
|
func intEnv(name string, fallback int) (int, error) {
|
|
value, ok := os.LookupEnv(name)
|
|
if !ok {
|
|
return fallback, nil
|
|
}
|
|
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(value))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%s: parse int: %w", name, err)
|
|
}
|
|
|
|
return parsed, nil
|
|
}
|
|
|
|
func boolEnv(name string, fallback bool) (bool, error) {
|
|
value, ok := os.LookupEnv(name)
|
|
if !ok {
|
|
return fallback, nil
|
|
}
|
|
|
|
parsed, err := strconv.ParseBool(strings.TrimSpace(value))
|
|
if err != nil {
|
|
return false, fmt.Errorf("%s: parse bool: %w", name, err)
|
|
}
|
|
|
|
return parsed, nil
|
|
}
|
|
|
|
func normalizeExporterValue(value string) string {
|
|
trimmed := strings.TrimSpace(value)
|
|
switch trimmed {
|
|
case "", "none":
|
|
return "none"
|
|
default:
|
|
return trimmed
|
|
}
|
|
}
|
|
|
|
func normalizeProtocolValue(primary string, fallback string, defaultValue string) string {
|
|
primary = strings.TrimSpace(primary)
|
|
if primary != "" {
|
|
return primary
|
|
}
|
|
|
|
fallback = strings.TrimSpace(fallback)
|
|
if fallback != "" {
|
|
return fallback
|
|
}
|
|
|
|
return strings.TrimSpace(defaultValue)
|
|
}
|