cfgx

package
v0.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2026 License: MIT Imports: 16 Imported by: 0

README

cfgx

A simple and flexible configuration management package for Go applications. Parse configuration from environment variables, command-line flags, and Docker secrets into a struct.

Features

  • Multiple sources: Environment variables, command-line flags, Docker secrets, and defaults
  • Priority-based: Higher priority sources override lower priority ones
  • Struct tags: Simple, declarative configuration using struct tags
  • Nested structs: Support for nested configuration with dot notation
  • Validation: Required field validation with clear error messages
  • Type safe: Supports string, int, and bool types
  • Auto-generated names: Environment and flag names generated from field names
  • Version support: Automatic version field population from build info

Installation

go get github.com/erlorenz/go-toolbox/cfgx

Quick Start

Define your configuration structure:

type Config struct {
    Version string // Auto-populated with BuildInfo.Main.Version
    Port    int    `env:"MY_PORT" short:"p" default:"8080"`
    DBString string `flag:"dsn" desc:"Database connection string"`

    // Nested structs use dot notation
    Log struct {
        Level string `default:"info"` // env=LOG_LEVEL flag=log-level
    }
}

Load configuration:

import "github.com/erlorenz/go-toolbox/cfgx"

func main() {
    var cfg Config

    if err := cfgx.Parse(&cfg, cfgx.Options{}); err != nil {
        log.Fatalf("Configuration error: %v", err)
    }

    log.Printf("Starting server on port %d", cfg.Port)
}

Configuration Sources

Sources are processed in priority order (highest to lowest):

  1. Command-line flags (priority: 100)
  2. Docker secrets (priority: 75)
  3. Environment variables (priority: 50)
  4. Default struct tags (priority: 0)
Environment Variables
type Config struct {
    Port int `env:"MY_PORT" default:"8080"`
}

Uses EnvPrefix option or field name converted to SCREAMING_SNAKE_CASE:

# Explicit env name
MY_PORT=9000 ./app

# Auto-generated from field name
PORT=9000 ./app

# With prefix
MY_APP_PORT=9000 ./app  # Options{EnvPrefix: "MY_APP"}
Command-line Flags
type Config struct {
    Port int `flag:"port" short:"p" desc:"Server port"`
}
./app --port=9000
./app -p 9000

Auto-generated flag names use kebab-case:

type Config struct {
    DatabaseURL string  // Becomes --database-url
}
Docker Secrets
type Config struct {
    APIKey string `dsec:"api_key"`
}

Reads from /run/secrets/api_key (or custom path via os.Root).

Default Values
type Config struct {
    LogLevel string `default:"info"`
    Port     int    `default:"8080"`
    Debug    bool   `default:"false"`
}

Struct Tags

Tag Description Example
env:"NAME" Override environment variable name env:"MY_PORT"
flag:"name" Override flag name flag:"port"
short:"x" Short flag alias short:"p"
default:"value" Default value default:"8080"
desc:"text" Help text description desc:"Server port"
optional:"true" Mark field as optional optional:"true"
dsec:"filename" Docker secret filename dsec:"api_key"

Version Management

The special Version field is automatically populated:

type Config struct {
    Version string  // Auto-populated
}
Development

During development with go run or go build, cfgx uses build info:

  • Returns version tag if available (e.g., v1.2.3)
  • Returns "(devel)" for development builds
Production

Inject version using -ldflags:

VERSION=$(git describe --tags --always --dirty)
go build -ldflags="-X main.Version=$VERSION" -o myapp

In your code:

var Version string  // Set via ldflags

func main() {
    cfg := Config{
        Version: Version,  // Highest priority
    }
    cfgx.Parse(&cfg, cfgx.Options{})
}

Docker example:

ARG VERSION=dev
RUN go build -ldflags="-X main.Version=${VERSION}" -o /app

Options

type Options struct {
    EnvPrefix string        // Prefix for environment variables
    SkipEnv   bool          // Skip environment variable parsing
    SkipFlags bool          // Skip command-line flag parsing
    Sources   []Source      // Custom configuration sources
}
Custom Sources

Implement the Source interface to add custom configuration sources:

type Source interface {
    Priority() int
    Process(map[string]ConfigField) error
}

Example:

type ConsulSource struct{}

func (s *ConsulSource) Priority() int {
    return 60  // Between env vars and docker secrets
}

func (s *ConsulSource) Process(fields map[string]ConfigField) error {
    // Fetch from Consul and set field values
    for path, field := range fields {
        if val, ok := consulGet(path); ok {
            field.Value.SetString(val)
        }
    }
    return nil
}

// Use it
cfgx.Parse(&cfg, cfgx.Options{
    Sources: []cfgx.Source{&ConsulSource{}},
})

Examples

Web Server Configuration
type Config struct {
    Version string

    Server struct {
        Host string `default:"0.0.0.0"`
        Port int    `env:"PORT" default:"8080"`
    }

    Database struct {
        URL      string `env:"DATABASE_URL"`
        MaxConns int    `default:"10"`
    }

    Log struct {
        Level  string `default:"info"`
        Format string `default:"json"`
    }
}

Run with:

PORT=3000 DATABASE_URL=postgres://... ./app --log-level=debug
Required Fields
type Config struct {
    APIKey string `env:"API_KEY"` // Required by default
    Port   int    `default:"8080" optional:"true"` // Optional
}

Error Handling

cfgx returns a MultiError containing all validation errors:

if err := cfgx.Parse(&cfg, cfgx.Options{}); err != nil {
    if multiErr, ok := err.(*cfgx.MultiError); ok {
        for _, e := range multiErr.Errors {
            log.Printf("Config error: %v", e)
        }
    }
    os.Exit(1)
}

Testing

For testing, skip sources you don't need:

func TestConfig(t *testing.T) {
    cfg := Config{Port: 9000}

    err := cfgx.Parse(&cfg, cfgx.Options{
        SkipEnv:   true,  // Don't read environment
        SkipFlags: true,  // Don't parse flags
    })

    if err != nil {
        t.Fatal(err)
    }
}

License

MIT

Documentation

Overview

Package cfgx provides functionality to parse configuration from multiple sources in a predictable precedence order with strong error handling and traceability. It is designed to be flexible enough for most applications while providing sensible defaults that follow Go idioms and best practices. with a defined precedence: command line args > environment variables > yaml files > defaults. It uses struct tags to customize field names and validation rules.

Index

Constants

View Source
const (
	PriorityDefault = 0   // Default values from struct tags
	PriorityEnv     = 50  // Environment variables
	PrioritySecrets = 75  // Docker secrets and other file-based secrets
	PriorityFlags   = 100 // Command-line flags
)

Priority levels for built-in sources. Custom sources can use any priority value. Higher priority sources override lower priority sources.

Variables

View Source
var DefaultConfigOptions = Options{
	ProgramName:   os.Args[0],
	EnvPrefix:     "",
	SkipFlags:     false,
	SkipEnv:       false,
	Args:          os.Args[1:],
	ErrorHandling: flag.ContinueOnError,
	Sources:       []Source{},
}

DefaultConfigOptions are the default set of configuration options. Each option can be overridden.

View Source
var (
	ErrNotPointerToStruct = errors.New("config must be a pointer to a struct")
)

Functions

func Parse

func Parse(cfg any, options Options) error

Parse populates the config struct from different sources. It follows this priority order (highest to lowest):

Command line arguments - 100, Environment variables - 50, Default values from struct tags - 0

To add a source in order, choose a priority in between the included sources. Add a top level field named Version to read the build info into it (as of 1.24 it uses the git tag).

Types

type ConfigField

type ConfigField struct {
	Path        string
	Value       reflect.Value
	Kind        reflect.Kind
	Name        string
	StructField reflect.StructField
	Tag         reflect.StructTag
	Description string
}

ConfigField represents a field in the config struct.

type DockerSecretsSource

type DockerSecretsSource struct {
	SecretsPath string
	FileContentSource
}

DockerSecretsSource wraps a FileContentSource. It reads the docker secret file at “/run/secrets/<secret_name>“. It defaults to snake case based on the struct path. Override the name with the tag "dsec".

func NewDockerSecretsSource added in v0.2.0

func NewDockerSecretsSource() *DockerSecretsSource

NewDockerSecretsSource sets a priority of PrioritySecrets (75), a tag of "dsec", and a secrets path of `/run/secrets`.

func (*DockerSecretsSource) Process added in v0.2.0

func (s *DockerSecretsSource) Process(structMap map[string]ConfigField) error

Process opens an os.Root and calls the underlying FileContentSource's Process method with the os.Root.FS.

type FileContentSource

type FileContentSource struct {
	PriorityLevel int
	Tag           string
	FS            fs.FS
}

FileContentSource reads individual files. It can be used for any files that live in the same directory or have explicit file locations. Do not use this directly, use one of the implementations, e.g. DockerSecretsSource. This allows to use an os.Root.FS in the implementation's Process method.

func (*FileContentSource) Priority

func (s *FileContentSource) Priority() int

Priority implements Source.

func (*FileContentSource) Process

func (s *FileContentSource) Process(structMap map[string]ConfigField) error

Process implements Source.

type MultiError

type MultiError struct {
	Errors []error `json:"errors"`
}

MultiError holds multiple errors that occurred during parsing.

func (*MultiError) Error

func (m *MultiError) Error() string

Error implements error interface.

type Options

type Options struct {
	// ProgramName is the name of the running program (defaults to os.Args[0]).
	ProgramName string
	// EnvPrefix looks adds a prefix to environment variable lookups.
	EnvPrefix string
	// SkipFlags ignores command line flags.
	SkipFlags bool
	// SkipEnv ignores environment variables.
	SkipEnv bool
	// Args provides command line arguments (defaults to os.Args[1:]).
	Args []string
	// ErrorHandling determines how parsing errors are handled.
	ErrorHandling flag.ErrorHandling
	// Sources adds additional sources.
	Sources []Source
}

Options holds options for the Parse function.

type Source

type Source interface {
	Priority() int
	Process(map[string]ConfigField) error
}

Source processes the configField map and applies values to the config struct. Choose a priority to process before or after other sources.

type ValidationError

type ValidationError struct {
	Field  string `json:"field"`
	Value  any    `json:"value"`
	Reason string `json:"reason"`
}

ValidationError represents a configuration validation error.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL