Documentation
¶
Overview ¶
Package config provides a production-ready configuration bootstrap for Go services built on top of Viper.
The package solves a common problem in service development: keeping configuration loading predictable across local development, CI, and deployment, without repeating boilerplate in every application.
It centralizes:
- default values
- local file discovery
- environment overrides
- optional file-less configuration via environment data (the "envvar" provider)
- optional pluggable remote sources via WithRemoteLoader
- final schema validation
An application integrates by implementing Configuration:
- SetDefaults(v Viper) to register application-specific defaults
- Validate() error to enforce final constraints
This is a Viper-based implementation of the configuration model described in: Nicola Asuni, 2014-09-13, "Software Configuration" https://technick.net/guides/software/software_configuration/
Configuration load order ¶
The effective configuration is built in this order (later steps override earlier ones):
- Built-in defaults from this package (log and shutdown settings), plus application defaults from SetDefaults.
- Local config file (default: config.json) searched in the explicit configDir (if provided) first, and then in: ./, $HOME/.<cmdName>/, /etc/<cmdName>/
- Environment variables for remote source selection: <PREFIX>_REMOTECONFIGPROVIDER, <PREFIX>_REMOTECONFIGENDPOINT, <PREFIX>_REMOTECONFIGPATH, <PREFIX>_REMOTECONFIGSECRETKEYRING, <PREFIX>_REMOTECONFIGDATA.
- Remote configuration loading, when configured: - provider "envvar": decodes base64 JSON from REMOTECONFIGDATA - any other provider: delegated to the application-supplied RemoteLoaderFunc registered with the WithRemoteLoader option
- Environment variables are also applied on the final Viper instance, so they can override file/remote values (useful for secrets and runtime overrides). Only keys registered with a default (via SetDefaults) or present in the config file/remote source are candidates for environment overrides: a key that has no default and appears nowhere else is not populated from the environment. Register a default for every configurable key to make it reliably env-overridable.
- Validate() is called on the final decoded config struct.
Why this matters ¶
Top features for developers:
- Predictable precedence model: easy to reason about which value wins.
- File-less deployments: the "envvar" provider loads the whole configuration from a single environment variable.
- Pluggable remote sources: WithRemoteLoader plugs in any remote storage backend (e.g. consul, etcd, vault, S3) without adding its client dependencies to this package.
- Sensible shared defaults: common log and shutdown settings are ready to use.
- Validation hook: fail fast on invalid runtime configuration.
- Testability: Viper is abstracted behind an interface for easy mocking.
Benefits ¶
Using this package reduces startup/config code in each service, improves configuration consistency across environments, and keeps configuration behavior explicit and auditable.
For a complete implementation example, see the Configuration implementation in examples/service/internal/cli/config.go and the Load call in examples/service/internal/cli/cli.go
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNilConfiguration is returned when a nil Configuration is passed to Load. ErrNilConfiguration = errors.New("configuration must not be nil") // ErrLocalConfig indicates a failure while loading the local configuration. ErrLocalConfig = errors.New("failed loading local configuration") // ErrRemoteConfig indicates a failure while loading the remote configuration. ErrRemoteConfig = errors.New("failed loading remote configuration") // ErrInvalidRemoteConfig indicates the remote-source selection settings are invalid. ErrInvalidRemoteConfig = errors.New("invalid remote source configuration") // ErrValidation indicates the final decoded configuration failed validation. ErrValidation = errors.New("failed validating configuration") // ErrMissingRemoteVar indicates a required remote-provider variable is not set. ErrMissingRemoteVar = errors.New("missing required remote configuration variable") )
Sentinel errors returned by Load and its helpers. Callers can match these with errors.Is to distinguish configuration failure modes.
var ErrViperRemoteNotRegistered = errors.New(`viper remote support is not registered: add a blank import of "github.com/spf13/viper/remote" to the application`)
ErrViperRemoteNotRegistered indicates that ViperRemoteLoader was used without registering the legacy Viper remote backends in the application via a blank import of github.com/spf13/viper/remote.
Functions ¶
func Load ¶
func Load(cmdName, configDir, envPrefix string, cfg Configuration, opts ...Option) error
Load builds cfg from defaults, local config, environment, and optional remote sources.
It is the package entry point that standardizes startup configuration loading so applications avoid duplicating Viper wiring and precedence logic.
The function applies the documented merge order, unmarshals into cfg, and executes cfg.Validate before returning. Optional behaviors (e.g. a custom remote source loader) can be enabled via opts.
func ViperRemoteLoader ¶ added in v1.148.0
func ViperRemoteLoader(rs *RemoteSourceConfig) (io.Reader, error)
ViperRemoteLoader is a RemoteLoaderFunc backed by the legacy Viper remote backends (consul, etcd, etcd3, firestore, nats), to be registered with the WithRemoteLoader option.
The backends and their client dependencies are NOT included in this package: the application MUST register them manually with a blank import of the viper remote package (which sets viper.RemoteConfig at init time):
import _ "github.com/spf13/viper/remote" // registers the legacy remote backends err := config.Load(cmdName, configDir, envPrefix, cfg, config.WithRemoteLoader(config.ViperRemoteLoader))
This keeps github.com/spf13/viper/remote (and its transitive backend clients) in the application module only. If the blank import is missing, loading from a remote provider fails with ErrViperRemoteNotRegistered.
The provider name must be one of viper.SupportedRemoteProviders (unknown names would otherwise be silently routed to the consul backend by the viper remote package), and both remoteConfigEndpoint and remoteConfigPath must be set (an empty endpoint would otherwise silently fall back to the backend client's default address, e.g. localhost:8500 for consul).
Types ¶
type BaseConfig ¶
type BaseConfig struct {
// Log configuration.
Log LogConfig `mapstructure:"log" validate:"required"`
// ShutdownTimeout is the time in seconds to wait for graceful shutdown.
ShutdownTimeout int64 `mapstructure:"shutdown_timeout" validate:"omitempty,min=1,max=3600"`
}
BaseConfig contains the default configuration options to be used in the application config struct.
type Configuration ¶
type Configuration interface {
// SetDefaults registers a default value for every configurable key.
//
// Registering a default for each key is required for environment-variable
// overrides to work: viper only applies an environment override to a key it
// already knows about (via a default, the config file, or a remote source).
// A key with no default that is absent from the file/remote source will not
// be populated from the environment. Use an empty value (e.g. "") when there
// is no meaningful default.
SetDefaults(v Viper)
// Validate enforces final constraints on the fully decoded configuration.
// It is invoked by Load after all sources have been merged.
Validate() error
}
Configuration is the interface we need the application config struct to implement.
type LogConfig ¶
type LogConfig struct {
// Level is the standard syslog level: EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
Level string `mapstructure:"level" validate:"required,oneof=EMERGENCY ALERT CRITICAL ERROR WARNING NOTICE INFO DEBUG"`
// Format is the log output format: CONSOLE, JSON.
Format string `mapstructure:"format" validate:"required,oneof=CONSOLE JSON"`
// Network is the optional network protocol used to send logs via syslog: udp, tcp.
Network string `mapstructure:"network" validate:"omitempty,oneof=udp tcp"`
// Address is the optional remote syslog network address: (ip:port) or just (:port).
Address string `mapstructure:"address" validate:"omitempty,hostname_port"`
}
LogConfig contains the configuration for the application logger.
type Option ¶ added in v1.148.0
type Option func(o *options)
Option configures optional Load behaviors.
func WithRemoteLoader ¶ added in v1.148.0
func WithRemoteLoader(fn RemoteLoaderFunc) Option
WithRemoteLoader registers the function used to load configuration data for any remoteConfigProvider other than the built-in "envvar" (e.g. consul, etcd, vault, S3, ...). The application supplies the implementation along with its client dependencies.
To restore the legacy Viper remote backends plug in the ready-made ViperRemoteLoader (see its documentation for the required setup).
type RemoteLoaderFunc ¶ added in v1.148.0
type RemoteLoaderFunc func(rs *RemoteSourceConfig) (io.Reader, error)
RemoteLoaderFunc loads raw configuration data (in the default JSON format) from an arbitrary remote source described by rs.
It is registered via WithRemoteLoader and invoked for any configured provider other than the built-in "envvar"; when no provider is configured the loader is not called. Returning a nil reader with a nil error means there is no remote data to merge. If the returned reader implements io.Closer it is closed after reading. This keeps remote-backend client dependencies out of this package: the application implements the fetch logic with whatever client library it needs.
type RemoteSourceConfig ¶ added in v1.148.0
type RemoteSourceConfig struct {
// Provider is the optional external configuration source: the built-in
// "envvar" or any name handled by a loader registered via WithRemoteLoader.
// When envvar is set the data should be set in the Data field.
Provider string `mapstructure:"remoteConfigProvider"`
// Endpoint is the remote configuration URL (ip:port), passed verbatim to the remote loader.
Endpoint string `mapstructure:"remoteConfigEndpoint"`
// Path is the remote configuration path where to search for the configuration data (e.g. "/cli/program").
// This is a path on the remote provider, not on the local filesystem.
Path string `mapstructure:"remoteConfigPath"`
// SecretKeyring is the path to an optional secret keyring the remote loader
// can use to decrypt the remote configuration data (e.g.: "/etc/program/configkey.gpg").
SecretKeyring string `mapstructure:"remoteConfigSecretKeyring"`
// Data is the base64 encoded JSON configuration data to be used with the "envvar" provider.
Data string `mapstructure:"remoteConfigData"`
}
RemoteSourceConfig contains the remote source options used to locate and load the optional remote configuration. It is passed to the RemoteLoaderFunc registered via WithRemoteLoader, which interprets and validates the fields it uses.
type Viper ¶
type Viper interface {
AddConfigPath(in string)
AllKeys() []string
AutomaticEnv()
BindEnv(input ...string) error
BindPFlag(key string, flag *pflag.Flag) error
Get(key string) any
ReadConfig(in io.Reader) error
ReadInConfig() error
SetConfigName(in string)
SetConfigType(in string)
SetDefault(key string, value any)
SetEnvKeyReplacer(r *strings.Replacer)
SetEnvPrefix(in string)
Unmarshal(rawVal any, opts ...viper.DecoderConfigOption) error
}
Viper is the local interface to the actual viper to allow for mocking.