goscopeconfig

package module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 8 Imported by: 0

README

go-scope-config

Go Reference Go Version Build Status Lint Status Coverage

Table of Contents

Go package for loading environment-based configurations (SCOPE) using Viper.

This package is particularly well-suited for Kubernetes environments, where the SCOPE variable (e.g., dev, staging, prod) can be easily injected into Pods as an environment variable, allowing the application to automatically pick the correct configuration file based on the cluster environment.

The environment variable name (SCOPE) is configurable to support different organizational standards.

Installation

go get github.com/arielsrv/go-scope-config

Usage

The package automatically looks for the SCOPE environment variable. If it's not defined, it uses dev by default.

It searches for files with the pattern config.[SCOPE].yaml or config.[SCOPE].yml in a folder (default is config/).

Configuration Merging (Inheritance)

If a config.common.yaml (or .yml) exists in the configuration directory, it will be loaded first as a base configuration. Then, the scope-specific file (config.[SCOPE].yaml) will be merged into it, overriding any shared keys.

graph TD
    Start([Start Load]) --> InitViper[Initialize Viper]
    InitViper --> LoadEnv[AutomaticEnv enabled]
    LoadEnv --> CheckCommon{config.common.yaml?}
    CheckCommon -- Exists --> ReadCommon[Read common config]
    CheckCommon -- Not Found --> CheckScope
    ReadCommon --> CheckScope{config.SCOPE.yaml?}
    CheckScope -- Exists --> MergeScope[Merge scope config]
    CheckScope -- Not Found --> Error([Error: Scope config not found])
    MergeScope --> FinalConfig([Final Configuration])
    
    subgraph Merging Logic
        ReadCommon
        MergeScope
    end
    
    style ReadCommon fill:#f9f,stroke:#333,stroke-width:2px
    style MergeScope fill:#bbf,stroke:#333,stroke-width:2px
    style Error fill:#fbb,stroke:#333,stroke-width:2px
Example File Structure
.
├── config/
│   ├── config.common.yaml (base values)
│   ├── config.dev.yaml    (overrides for dev)
│   └── config.prod.yml    (overrides for prod)
└── main.go
Code
Using LoadDefault (Autoloader)

The quickest way to start with default options:

package main

import (
    "fmt"
    "log/slog"
    "os"
    goscopeconfig "github.com/arielsrv/go-scope-config"
)

func main() {
    // Local logger to avoid using the global slog
    logger := slog.Default()

    // Loads from "config/" folder using SCOPE env var (defaults to "dev")
    v, err := goscopeconfig.LoadDefault()
    if err != nil {
        logger.Error("Error loading default config", "error", err)
        os.Exit(1)
    }

    fmt.Printf("App Name: %s\n", v.GetString("app.name"))
}
Automatic Autoloader (init function)

The package provides two ways to automatically load the configuration.

1. Using DefaultViper (named import)

If you import the package with a name, you can access the pre-initialized DefaultViper instance.

package main

import (
    "fmt"
    "log/slog"
    "os"
    goscopeconfig "github.com/arielsrv/go-scope-config"
)

func main() {
    // Local logger to avoid using the global slog
    logger := slog.Default()

    // Check if there was an error during automatic loading
    if goscopeconfig.ErrLoad != nil {
        logger.Error("Error loading config", "error", goscopeconfig.ErrLoad)
        os.Exit(1)
    }

    // Use the pre-loaded instance
    v := goscopeconfig.DefaultViper
    fmt.Printf("App Name: %s\n", v.GetString("app.name"))
}
2. Using Blank Import (global Viper)

If you want to use the standard github.com/spf13/viper package directly, you can use the autoload sub-package with a blank import. This will load the configuration into Viper's global instance (the singleton returned by viper.GetViper()).

[!IMPORTANT] This is distinct from the DefaultViper provided by the root package, which uses its own isolated Viper instance.

package main

import (
    "fmt"
    _ "github.com/arielsrv/go-scope-config/autoload"
    "github.com/spf13/viper"
)

func main() {
    // Configuration is already loaded into global viper
    fmt.Printf("App Name: %s\n", viper.GetString("app.name"))
}
Manual Initialization (With Options)
package main

import (
    "fmt"
    "log/slog"
    "os"
    goscopeconfig "github.com/arielsrv/go-scope-config"
)

func main() {
    // Local logger to avoid using the global slog
    logger := slog.Default()

    // Initializes the loader. 
    // By default, it looks in the "config/" folder and reads the
    // "SCOPE" environment variable.
    loader := goscopeconfig.New(
        goscopeconfig.WithConfigDir("custom_configs"),
        goscopeconfig.WithScopeEnv("APP_ENV"),
    )

    if err := loader.Load(); err != nil {
        logger.Error("Error loading config", "error", err)
        os.Exit(1)
    }

    // Access values via Viper
    v := loader.Viper()
    fmt.Printf("App Name: %s\n", v.GetString("app.name"))
    fmt.Printf("Current Scope: %s\n", loader.GetScope())
}

Architecture

The ConfigLoader is the core component. It uses options for configuration:

Loader API
  • loader.Load(): Executes the loading and merging.
  • loader.Viper(): Returns the internal *viper.Viper instance.
  • loader.GetScope(): Returns the current detected scope.
  • loader.GetConfigPath(): Returns the absolute path of the loaded configuration file.

Examples

You can find complete examples in the examples/ folder:

  • blank-import: Usage with blank import (global Viper).
  • autoloader: Quick start using LoadDefault().
  • automatic: Accessing the pre-initialized DefaultViper.
  • with-logger: Integrating with slog (JSON format).
  • custom-scope-env: Using a custom environment variable for scope.
  • custom-dir: Loading configurations from a non-standard directory.
  • merge-common: Demonstrating configuration inheritance and overrides.
  • uber-fx: Integration with the Uber-FX dependency injection framework.
  • docker-compose: Running inside a container with Docker Compose, demonstrating environment variable inheritance.
Run examples

The examples are in a separate module. To run them, go to the examples directory:

cd examples

Then run the desired example:

go run simple/main.go
go run custom-dir/main.go
go run merge-common/main.go
go run autoloader/main.go
go run automatic/main.go
go run blank-import/main.go
go run with-logger/main.go
go run custom-scope-env/main.go
go run uber-fx/main.go
Docker Compose Example

To run the Docker Compose example:

cd examples/docker-compose
docker compose up

Logger Support

You can provide a logger that satisfies the Logger interface (which has a Printf(format string, v ...any) method). This allows easy integration with both the standard log package and modern loggers like slog.

// Example with slog (JSON format)
handler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(handler)

// Small wrapper to satisfy the Printf interface
type slogWrapper struct { logger *slog.Logger }
func (s *slogWrapper) Printf(f string, v ...any) {
    s.logger.Info(fmt.Sprintf(f, v...))
}

loader := goscopeconfig.New(
    goscopeconfig.WithLogger(&slogWrapper{logger: logger}),
)
loader.Load()

Commands

The project uses Taskfile to manage common tasks:

  • task test: Run unit tests.
  • task lint: Run linters (golangci-lint, gofumpt, betteralign).
  • task audit: Verify modules, run go vet and govulncheck.
  • task markdown: Lint markdown files.
  • task coverage: Generate and show coverage report.
  • task clean: Remove coverage and temporary files.
  • task build: Build the project.

Configuration Value Priority

The package uses Viper's resolution order. When the same key is defined in multiple sources, the following priority applies (highest wins):

Priority Source Example
1 — Highest Environment variables (AutomaticEnv) APP_NAME=myapp
2 Scope-specific config file config.prod.yaml
3 — Lowest Common config file config.common.yaml

Key-mapping rule: dots (.) in YAML keys are replaced by underscores (_) in environment variable names.

app.name  →  APP_NAME
app.port  →  APP_PORT
db.host   →  DB_HOST

[!IMPORTANT] Environment variables always override any value defined in the YAML files. This follows the 12-Factor App principle and is especially useful in containerised environments (Docker, Kubernetes) where secrets or runtime values are injected via env vars.

Override Examples
# Overrides app.name regardless of what is in the YAML files
APP_NAME=production-service go run main.go

# Works even if the key does NOT exist in any YAML file
NEW_KEY=value go run main.go   # v.GetString("new.key") → "value"

Environment Variables

Variable Default Description
SCOPE dev Selects the scope config file. Configurable via WithScopeEnv.
<ANY_KEY> - Overrides YAML values at runtime (dots replaced by underscores).

Documentation

Overview

Package goscopeconfig provides a simple and flexible way to load configurations based on an environment scope (e.g., dev, prod, staging).

It uses spf13/viper under the hood and allows for configuration inheritance by merging a common configuration file with scope-specific overrides.

Basic usage:

loader := goscopeconfig.New()
if err := loader.Load(); err != nil {
    log.Fatal(err)
}
v := loader.Viper()
fmt.Println(v.GetString("app.name"))

Using the autoloader:

v, err := goscopeconfig.LoadDefault()
if err != nil {
    log.Fatal(err)
}
fmt.Println(v.GetString("app.name"))

For automatic initialization using the global Viper instance, see the autoload subpackage.

Index

Constants

This section is empty.

Variables

View Source
var (
	// DefaultViper is the automatically loaded Viper instance.
	// It is initialized during package loading via init().
	DefaultViper *viper.Viper //nolint:gochecknoglobals // public API: exported global intentionally set by init()
	// ErrLoad stores any error encountered during automatic loading.
	ErrLoad error
)

Functions

func LoadDefault

func LoadDefault() (*viper.Viper, error)

LoadDefault is a shortcut that creates a new ConfigLoader with default options, loads the configuration, and returns the internal Viper instance. This is useful for quick starts where default behavior is enough.

Types

type ConfigLoader

type ConfigLoader struct {
	// contains filtered or unexported fields
}

ConfigLoader handles loading configurations based on the scope.

func New

func New(opts ...Option) *ConfigLoader

New creates a new ConfigLoader instance with the provided options.

func NewWithViper

func NewWithViper(viper *viper.Viper, opts ...Option) *ConfigLoader

NewWithViper creates a new ConfigLoader instance using a specific Viper instance.

func (*ConfigLoader) GetConfigPath

func (r *ConfigLoader) GetConfigPath() string

GetConfigPath returns the absolute path of the loaded configuration file (the scope-specific one).

func (*ConfigLoader) GetScope

func (r *ConfigLoader) GetScope() string

GetScope returns the current scope being used.

func (*ConfigLoader) Load

func (r *ConfigLoader) Load() error

Load loads the configuration according to the current scope. It looks for config.common.yaml/yml first and then merges with config.[scope].yaml/yml. The scope-specific configuration overrides values in the common one.

func (*ConfigLoader) Viper

func (r *ConfigLoader) Viper() *viper.Viper

Viper returns the internal viper instance to access values.

type Defaults added in v1.1.6

type Defaults struct {
	ConfigDir  string
	Scope      string
	ScopeEnv   string
	CommonName string
}

Defaults contain the default configuration values.

func DefaultConfig added in v1.1.6

func DefaultConfig() Defaults

DefaultConfig returns the default configuration values.

type Logger added in v1.0.5

type Logger interface {
	Printf(format string, v ...any)
}

Logger is a simple interface for logging. Many logging libraries satisfy this interface (e.g., standard log.Logger, logrus).

type Option

type Option func(*ConfigLoader)

Option defines a function to configure the ConfigLoader.

func WithConfigDir

func WithConfigDir(dir string) Option

WithConfigDir allows specifying a custom folder for the configurations.

func WithLogger added in v1.0.5

func WithLogger(logger Logger) Option

WithLogger allows providing a logger to show loaded files.

func WithScope

func WithScope(scope string) Option

WithScope allows forcing a specific scope, ignoring the environment variable.

func WithScopeEnv added in v1.0.8

func WithScopeEnv(envName string) Option

WithScopeEnv allows specifying a custom environment variable to load the scope from. Default is SCOPE.

Directories

Path Synopsis
Package autoload provides automatic configuration loading into the global Viper instance.
Package autoload provides automatic configuration loading into the global Viper instance.

Jump to

Keyboard shortcuts

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