Simple configuration package for Go applications. It is easy to use and supports environment variables as the highest-priority source (for 12-Factor apps).
go get github.com/virp/confpackage main
import (
"fmt"
"log"
"time"
"github.com/virp/conf"
)
type RedisConfig struct {
Addr string `conf:"required"`
Password string
DB int `conf:"default:0"`
Endpoints []string `conf:"default:localhost:6379;localhost:6380"`
Timeout time.Duration `conf:"default:5s"`
}
type AWS struct {
Key string `conf:"required,env:AWS_ACCESS_KEY_ID"`
Secret string `conf:"required,env:AWS_SECRET_ACCESS_KEY"`
Region string `conf:"default:eu-central-1,env:AWS_DEFAULT_REGION"`
}
type Config struct {
Debug bool
Redis RedisConfig
AWS AWS
URLs map[string]string `conf:"default:api:https://api.example.com;admin:https://admin.example.com"`
}
func main() {
var cfg Config
if err := conf.Parse("my_service", &cfg); err != nil {
log.Fatal(err)
}
fmt.Printf("Debug: %t\n", cfg.Debug) // MY_SERVICE_DEBUG
fmt.Printf(
"Redis:\n\tAddr: %s\n\tPassword: %s\n\tDB: %d\n",
cfg.Redis.Addr, // MY_SERVICE_REDIS_ADDR
cfg.Redis.Password, // MY_SERVICE_REDIS_PASSWORD
cfg.Redis.DB, // MY_SERVICE_REDIS_DB
)
fmt.Printf(
"AWS\n\tKey: %s\n\tSecret: %s\n\tRegion: %s\n",
cfg.AWS.Key, // AWS_ACCESS_KEY_ID or MY_SERVICE_AWS_ACCESS_KEY_ID
cfg.AWS.Secret, // AWS_SECRET_ACCESS_KEY or MY_SERVICE_AWS_SECRET_ACCESS_KEY
cfg.AWS.Region, // AWS_DEFAULT_REGION or MY_SERVICE_AWS_DEFAULT_REGION
)
}Use ParseYaml to load defaults and YAML values first, then override them with environment variables when they are set:
data, err := os.Open("config.yaml")
if err != nil {
log.Fatal(err)
}
defer data.Close()
var cfg Config
if err := conf.ParseYaml(data, &cfg); err != nil {
log.Fatal(err)
}ParseYaml does not accept an env prefix. Generated env names use the same rules as Parse with an empty prefix:
type Config struct {
Debug bool // DEBUG
Redis RedisConfig // REDIS_ADDR
}Explicit env names still have priority over generated names:
type AWS struct {
Key string `conf:"required,env:AWS_ACCESS_KEY_ID"` // AWS_ACCESS_KEY_ID or KEY
}YAML keys use yaml tags. Without a yaml tag, field names are converted to lower snake case:
type RedisConfig struct {
Addr string `conf:"required"` // addr
Endpoints []string `yaml:"redis_servers"` // redis_servers
Secret string `yaml:"-"` // ignored in YAML
}Nested structs are read from nested YAML mappings, and embedded structs are read inline. YAML sequences and mappings are assigned natively, so slices and maps do not use the env string formats:
debug: true
redis:
addr: localhost:6379
redis_servers:
- localhost:6379
- localhost:6380
urls:
api: https://api.example.com
admin: https://admin.example.comField names are converted from CamelCase to upper snake case and prefixed:
type Config struct {
Debug bool // MY_SERVICE_DEBUG
HTTPPort int // MY_SERVICE_HTTP_PORT
RedisURL string // MY_SERVICE_REDIS_URL
}
_ = conf.Parse("my_service", &cfg)Nested structs append their field name to the prefix:
type Config struct {
Redis RedisConfig
}
type RedisConfig struct {
Addr string // MY_SERVICE_REDIS_ADDR
}An empty prefix uses only the field name, for example DEBUG.
Supported conf tag options:
required: fail if no env variable is set.default:value: set a default before reading env variables.env:NAME: readNAMEfirst, then fall back to the generated env name.-: skip the field.
Unknown tag options are treated as errors.
type Config struct {
Addr string `conf:"required"`
Port int `conf:"default:8080"`
Region string `conf:"default:eu-central-1,env:AWS_DEFAULT_REGION"`
Secret string `conf:"-"`
}required and default cannot be used together.
Parse reads values from os.LookupEnv. Use ParseWithLookup when tests or tools need a custom env-like source:
env := map[string]string{
"MY_SERVICE_DEBUG": "true",
"MY_SERVICE_REDIS_ADDR": "localhost:6379",
}
lookup := func(key string) (string, bool) {
value, ok := env[key]
return value, ok
}
var cfg Config
if err := conf.ParseWithLookup("my_service", &cfg, lookup); err != nil {
log.Fatal(err)
}Built-in parsing supports:
string- signed and unsigned integers
boolfloat32,float64time.Duration- slices, separated by
; - maps, formatted as
key:value;key:value - pointers to supported types and nested structs
Map values may contain ::
type Config struct {
Routes map[string]string `conf:"default:api:https://api.example.com:443;admin:http://localhost:8080"`
}Custom types can implement conf.Setter, encoding.TextUnmarshaler, or encoding.BinaryUnmarshaler.
type Token string
func (t *Token) Set(value string) error {
*t = Token("Bearer " + value)
return nil
}
type Config struct {
APIToken Token `conf:"required,env:API_TOKEN"`
}