132 lines
3.3 KiB
Go
132 lines
3.3 KiB
Go
// Package config provides types and methods for managing
|
|
// app config. A go-app configuration can be extended by any type
|
|
// that embeds AppConfig, and has helpers to generate json
|
|
// schema merged with the custom type's fields
|
|
package config
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
"github.com/caarlos0/env/v11"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Version is to be set by ldflags in go build command or
|
|
// retrieved from build meta below.
|
|
var Version = "(devel)"
|
|
|
|
// LoadConfig will try to load from config if -config is
|
|
// provided as a file, and will apply any environment overrides
|
|
// on-top of configuration defaults.
|
|
// Config is stored in returned context, and can be retrieved
|
|
// using config.FromCtx(ctx).
|
|
func LoadConfig(ctx context.Context) (context.Context, error) {
|
|
configPath := flag.String("config", "", "Path to the configuration file")
|
|
flag.Parse()
|
|
|
|
// Start with defaults
|
|
// Load from config if provided
|
|
// Layer on environment
|
|
cfg, err := loadConfig(*configPath)
|
|
if err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
// Add config to context, and return
|
|
// an updated context
|
|
ctx = cfg.AddToCtx(ctx)
|
|
return ctx, nil
|
|
}
|
|
|
|
// loadConfig loads the application configuration from the specified path,
|
|
// applying environment variable overrides.
|
|
func loadConfig(configPath string) (*AppConfig, error) {
|
|
cfg := *DefaultConfig
|
|
|
|
if configPath != "" {
|
|
file, err := os.Open(configPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not open config file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
decoder := yaml.NewDecoder(file)
|
|
if err := decoder.Decode(&cfg); err != nil {
|
|
return nil, fmt.Errorf("could not decode config file: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := env.Parse(&cfg); err != nil {
|
|
return nil, fmt.Errorf("could not parse environment variables: %w", err)
|
|
}
|
|
|
|
// Perform updates / enrichments
|
|
err := prepareConfig(&cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// prepareConfig enriches and validates the AppConfig, parsing duration strings
|
|
// for HTTP timeouts.
|
|
func prepareConfig(cfg *AppConfig) error {
|
|
var errs error
|
|
|
|
// Set timeouts
|
|
if cfg.HTTP.ReadTimeout != "" {
|
|
if rT, err := time.ParseDuration(cfg.HTTP.ReadTimeout); err == nil {
|
|
cfg.HTTP.rT = &rT
|
|
} else {
|
|
errs = errors.Join(errs, err)
|
|
}
|
|
}
|
|
|
|
if cfg.HTTP.ReadTimeout != "" {
|
|
if wT, err := time.ParseDuration(cfg.HTTP.WriteTimeout); err == nil {
|
|
cfg.HTTP.wT = &wT
|
|
} else {
|
|
errs = errors.Join(errs, err)
|
|
}
|
|
}
|
|
|
|
if cfg.HTTP.IdleTimeout != "" {
|
|
if iT, err := time.ParseDuration(cfg.HTTP.IdleTimeout); err == nil {
|
|
cfg.HTTP.iT = &iT
|
|
} else {
|
|
errs = errors.Join(errs, err)
|
|
}
|
|
}
|
|
|
|
// Prepare user-provided expressions, panic up-front if invalid
|
|
cfg.HTTP.excludeRegexps = make([]*regexp.Regexp, len(cfg.HTTP.LogExcludePathRegexps))
|
|
for i, re := range cfg.HTTP.LogExcludePathRegexps {
|
|
cfg.HTTP.excludeRegexps[i] = regexp.MustCompile(re)
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
// Timeouts returns read timeout, write timeout, and idle timeout, in that order.
|
|
// Returns nil if unset.
|
|
func (h *HTTPConfig) Timeouts() (*time.Duration, *time.Duration, *time.Duration) {
|
|
return h.rT, h.wT, h.iT
|
|
}
|
|
|
|
// getVersion returns the application version, preferring the build info version
|
|
// over the compile-time set Version variable.
|
|
func getVersion() string {
|
|
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" {
|
|
return info.Main.Version
|
|
}
|
|
return Version
|
|
}
|