Documentation
¶
Overview ¶
Package config loads configuration from the environment into a struct, once, at boot — and fails the boot when something required is missing.
The failure mode this exists to kill: `os.Getenv` scattered through the code, read lazily, so a missing variable is discovered by the first request that needs it at 2am rather than by the process refusing to start. Declare the whole surface as a struct, call Load, and a misconfigured deployment cannot come up.
type Config struct {
Port int `env:"PORT" default:"8080"`
DSN string `env:"DATABASE_URL" required:"true" secret:"true"`
Timeout time.Duration `env:"HTTP_TIMEOUT" default:"30s"`
Origins []string `env:"CORS_ORIGINS" default:"*"`
Redis RedisConfig `envPrefix:"REDIS_"`
}
Example ¶
The whole configuration surface is one struct, loaded once. LoadFrom takes a lookup so a test needs no environment mutation.
package main
import (
"fmt"
"time"
"github.com/IncepTools/go/src/core/config"
)
type redisConfig struct {
Addr string `env:"ADDR" default:"localhost:6379"`
DB int `env:"DB" default:"0"`
}
type appConfig struct {
Port int `env:"PORT" default:"8080"`
RequestTimeout time.Duration `env:"REQUEST_TIMEOUT" default:"30s"`
Origins []string `env:"CORS_ORIGINS" default:"https://app.example.com"`
DatabaseURL string `env:"DATABASE_URL" required:"true" secret:"true"`
Redis redisConfig `envPrefix:"REDIS_"`
}
func main() {
var cfg appConfig
err := config.LoadFrom(&cfg, config.MapLookup(map[string]string{
"DATABASE_URL": "postgres://user:hunter2@db/shop",
"REDIS_ADDR": "redis.internal:6379",
}))
if err != nil {
panic(err)
}
fmt.Println(cfg.Port) // from the default
fmt.Println(cfg.RequestTimeout) // parsed as a duration, not nanoseconds
fmt.Println(cfg.Origins)
fmt.Println(cfg.Redis.Addr) // the prefixed variable
fmt.Println(cfg.Redis.DB)
}
Output: 8080 30s [https://app.example.com] redis.internal:6379 0
Index ¶
Examples ¶
Constants ¶
const ( TagEnv = "env" // variable name TagDefault = "default" // value used when the variable is unset or empty TagRequired = "required" // "true" to fail the load when unset TagPrefix = "envPrefix" // prefix applied to a nested struct's variables TagSecret = "secret" // "true" to mask the value in Describe output TagSep = "sep" // slice separator, default "," )
Tag names read by Load.
Variables ¶
This section is empty.
Functions ¶
func Describe ¶
Describe returns one line per configured variable, with any field tagged `secret:"true"` masked. Log it at boot: knowing what the process actually read beats guessing which ConfigMap won.
Example ¶
Describe is safe to log at boot: anything tagged secret is masked.
package main
import (
"fmt"
"time"
"github.com/IncepTools/go/src/core/config"
)
type redisConfig struct {
Addr string `env:"ADDR" default:"localhost:6379"`
DB int `env:"DB" default:"0"`
}
type appConfig struct {
Port int `env:"PORT" default:"8080"`
RequestTimeout time.Duration `env:"REQUEST_TIMEOUT" default:"30s"`
Origins []string `env:"CORS_ORIGINS" default:"https://app.example.com"`
DatabaseURL string `env:"DATABASE_URL" required:"true" secret:"true"`
Redis redisConfig `envPrefix:"REDIS_"`
}
func main() {
cfg := appConfig{
Port: 8080,
DatabaseURL: "postgres://user:hunter2@db/shop",
}
for _, line := range config.Describe(&cfg) {
fmt.Println(line)
}
}
Output: CORS_ORIGINS=[] DATABASE_URL=po********op PORT=8080 REDIS_ADDR= REDIS_DB=0 REQUEST_TIMEOUT=0s
func Load ¶
Load fills dst (a non-nil pointer to a struct) from the process environment.
Example (ReportsEveryProblemAtOnce) ¶
Every problem is reported in one pass. Fixing configuration one restart at a time wastes a deploy each round.
package main
import (
"fmt"
"github.com/IncepTools/go/src/core/config"
)
func main() {
type cfg struct {
DSN string `env:"DATABASE_URL" required:"true"`
Port int `env:"PORT"`
}
var c cfg
err := config.LoadFrom(&c, config.MapLookup(map[string]string{
"PORT": "eighty",
}))
fmt.Println(err)
}
Output: config: 2 problem(s): - DATABASE_URL is required but not set - PORT: "eighty" is not an integer
func LoadDotEnv ¶
LoadDotEnv reads a .env file and sets any variable that is not ALREADY in the environment. The precedence is deliberate: a real environment variable always wins, so a committed .env cannot override what a deployment sets.
A missing file is not an error — that is the normal production case.