Configuration¶
The Configuration component provides a flexible and powerful abstraction over the spf13/viper configuration library. It delivers enhanced functionality for configuration loading and management while adding crucial testability features that are not available with viper directly.
Overview¶
The configuration system is built around the Containable interface and the Container struct, providing a unified API for accessing configuration values regardless of their source. The package adds several key improvements over raw viper usage:
Enhanced Testability: Unlike viper, which is difficult to mock effectively, the Containable interface enables clean dependency injection and comprehensive testing strategies.
Observer Pattern: Adds filesystem watching with an observer pattern for configuration changes, allowing your application to react to configuration updates automatically.
Simplified API: Provides convenience methods for common configuration tasks while maintaining access to the underlying viper instance when needed.
Multiple Source Support: Handles configuration loading from files, embedded resources, environment variables, and command-line flags with automatic merging and type conversion.
Why use the Wrapper?¶
Instead of industrializing Viper directly in your application code, GTB provides the Containable interface. This allows us to:
- Enforce Consistency: Methods like
NewFilesContainerensure that every CLI tool follows the same logic for loading and merging configuration files. - Abstract the Filesystem: We integrate natively with
afero, meaning your configuration can be loaded from the OS, an in-memory test buffer, or embedded assets through the same interface. - Automate Environment Mapping: We pre-configure environment variable replacement (e.g.,
server.portbecomesSERVER_PORT) so you don't have to.
Core Interface¶
The Containable interface provides the primary API for configuration access:
[!NOTE] See pkg.go.dev/gitlab.com/phpboyscout/go-tool-base/pkg/config for the full API definition.
Container Implementation¶
The Container struct is the primary implementation of the Containable interface. Engineers should use this concrete type rather than the interface directly, except for testing and dependency injection. Its fields are internal — construct it via the options-pattern factory functions:
func NewFilesContainer(fs afero.Fs, opts ...ContainerOption) *Container
func NewReaderContainer(fs afero.Fs, opts ...ContainerOption) *Container
[!NOTE] See pkg.go.dev/gitlab.com/phpboyscout/go-tool-base/pkg/config for the full
ContainerAPI.
Container Options¶
All factory functions accept functional options to configure container behavior. The only required argument is fs afero.Fs:
config.WithLogger(l logger.Logger) // Logger (default: noop)
config.WithEnvPrefix(prefix string) // Env var prefix (default: none)
config.WithConfigFiles(files ...string) // Config file paths
config.WithConfigFormat(format string) // "yaml", "json", "toml"
config.WithConfigReaders(readers ...io.Reader) // io.Reader config sources
config.WithSchema(schema *Schema) // Validation schema
In this section¶
- Creating Containers — factory functions and choosing the right one
- Sources & Precedence — file, embedded, environment, dotenv, and how they merge
- Schema Validation — validate configuration against a schema
- Hot-Reload & Observers — react to live configuration changes
- Best Practices & Integration — patterns, GTB integration, sensitive-value masking
For test recipes (in-memory containers, the generated mocks, testing observers), see the Test Code That Uses Configuration how-to guide.