Simple, useful and opinionated config loader.

aconfig

build-img pkg-img reportcard-img coverage-img

Simple, useful and opinionated config loader.

Rationale

There are many solutions regarding configuration loading in Go. I was looking for a simple loader that will as much as possible and be easy to use and understand. The goal was to load config from 4 places: defaults (in the code), files, environment variables, command-line flags. This library works with all of this sources.

Features

  • Simple API.
  • Clean and tested code.
  • Automatic fields mapping.
  • Supports different sources:
    • defaults in the code
    • files (JSON, YAML, TOML, DotENV, HCL)
    • environment variables
    • command-line flags
  • Dependency-free (file parsers are optional).
  • Ability to walk over configuration fields.

Install

Go version 1.14+

go get github.com/cristalhq/aconfig

Example

type MyConfig struct {
	Port int `default:"1111" usage:"just give a number"`
	Auth struct {
		User string `default:"def-user"`
		Pass string `default:"def-pass"`
	}
	Pass string `default:"" env:"SECRET" flag:"sec_ret"`
}

var cfg MyConfig
loader := aconfig.LoaderFor(&cfg, aconfig.Config{
	// feel free to skip some steps :)
	// SkipDefaults: true,
	// SkipFiles:    true,
	// SkipEnv:      true,
	// SkipFlags:    true,
	EnvPrefix:       "APP",
	FlagPrefix:      "app",
	Files:           []string{"/var/opt/myapp/config.json", "ouch.yaml"},
	FileDecoders: map[string]aconfig.FileDecoder{
		// from `aconfigyaml` submodule
		// see submodules in repo for more formats
		".yaml": aconfigyaml.New(),
	},
})

// IMPORTANT: define your own flags with `flagSet`
flagSet := loader.Flags()

if err := loader.Load(); err != nil {
	panic(err)
}

// configuration fields will be loaded from (in order):
//
// 1. defaults set in structure tags (see MyConfig defenition)
// 2. loaded from files `file.json` if not `ouch.yaml` will be used
// 3. from corresponding environment variables with the prefix `APP_`
// 4. command-line flags with the prefix `app.` if they are 

Also see examples: examples_test.go.

Integration with spf13/cobra playground.

Documentation

See these docs.

License

MIT License.

Comments
  • docs: fix README typo

    docs: fix README typo

    Hi there 👋

    Great library, I like you style! I've written a similar one (although it's limited to environment variables), maybe we could share some inspiration.

    Would you like some assistance with any of the open issues? I'd be happy to help!

  • Затруднение при запуске тестов из-под VSCode

    Затруднение при запуске тестов из-под VSCode

    Добрый день. Очень нравиться этот пакет, но возникли сложности. При попытке использовать библиотеку при запуске тестов выходит ошибка о неизвестном(not defined) флаге. запуск осуществляется через VSCode контекстным пунктом или "run test" или "debug test" Вывод:

    Running tool: C:\Go\bin\go.exe test -timeout 30s -run ^TestAliasType_send$ package
    
    flag provided but not defined: -test.testlogfile
    Usage:
    

    Далее идет вывод использования программы как будто указан неверный флаг или указан "-help" И возвращается ошибка с текстом: flag provided but not defined: -test.testlogfile параметр в конфиге AllowUnknownFlags: true, стоит, версия github.com/cristalhq/aconfig v0.14.1 Я что-то делаю не так?

  • If FileFlag set and not use, aconfig override Files with empty string

    If FileFlag set and not use, aconfig override Files with empty string

    Config example:

    aconfig.Config{
    		SkipFiles:          false,
    		SkipFlags:          false,
    		FailOnFileNotFound: true,
    		MergeFiles:         false,
    		FileFlag:           "cfg",
    		Files:              []string{"conf/settings.yaml"},
    

    if run binary without -cfg flag, Files become []string{""}

  • Only pre-set file extensions are supported

    Only pre-set file extensions are supported

    This does not work:

    aconfig.LoaderFor(dest, aconfig.Config{
    	Files:            files,
    	FileDecoders: map[string]aconfig.FileDecoder{
    		".yaml": aconfigyaml.New(),
    		".yml":  aconfigyaml.New(),
    	},
    })
    

    .yml files cannot be loaded and fail with cannot load config: unknown field in file error, because the struct tags are only auto-generated for the pre-set formats. The file format is then recognized via filepath.Ext, which is yml and not yaml.

    IMHO the "format" should be returned by the file decoder, maybe as another interface method. I can try to prepare a PR if you'd like to go that way.

    EDIT: example of how it might look like without breaking backwards compatibility: https://github.com/avast/aconfig/commit/fbab7665e54111e1cc90a431de747afdbcf7bbff

  • Fix deep file parsing not working when flag delimiter is set

    Fix deep file parsing not working when flag delimiter is set

    File parsing with nested structure doesn't work currently when a different flag delimiter than default "." is set. So I tried to fix that here.

    Couldn't run the full test suite on my machine, so I am not 100% sure that this change doesn't break anything else.

  • Update README.md

    Update README.md

    Improve grammar and flow of text

    Improve readability of example

    Bump required Go version to match https://github.com/cristalhq/aconfig/blob/main/aconfigyaml/go.mod

  • Description of configs

    Description of configs

    Problem

    Having an option to generate default flags is really nice! but it's not that useful if I cannot write help for this. So for example:

    ❯❯❯ ./samplego --help
    Usage:
      -myauth.lease string
        	 (default "3s")
      -myauth.multiple string
    
      -myauth.mypass string
        	 (default "def-pass")
      -myauth.myuser string
        	 (default "def-user")
      -port string
    

    I want to communicate to a user what are those properties.

    Proposal

    Ideally, I'd like to put a comment like this

    type MyAuth struct {
    	// user that will use the server
    	MyUser string `yaml:"myUser" default:"def-user"`
    }
    

    but I don't think it's technically possible to then use it in the code (unless some generator that will parse it?)

    so the other option would be to introduce a tag for it.

    type MyAuth struct {
    	MyUser string `yaml:"myUser" default:"def-user" description:"user that will user server"`
    }
    

    This way I could even reuse it and autogenerate generate configuration for yaml and envvars

  • yaml decorder can't open file in embed file system

    yaml decorder can't open file in embed file system

    in the aconfigyaml package, DecodeFile func use os.Open to open file, when in embed file system, it can't find file then throw an err: "no such file or directory"。 but when using default json decorder, jsonDecoder has a fsys field, which can open file in embed file system.

    hope you can fix this bug in aconfigyaml package.

  • Panic on parsing YAML configuration with empty lists

    Panic on parsing YAML configuration with empty lists

    Hi,

    I encounter panic when a list in the YAML configuration is empty:

    package config_test
    
    import (
    	"testing"
    
    	"github.com/cristalhq/aconfig"
    	"github.com/cristalhq/aconfig/aconfigyaml"
    )
    
    type (
    	ResourcesConfiguration struct {
    		ResourcesA []ResourceA `yaml:"resources_a"`
    		ResourcesB []ResourceB `yaml:"resources_b"`
    	}
    
    	ResourceA struct {
    		Field string `yaml:"field"`
    	}
    
    	ResourceB struct {
    		Field int `yaml:"field"`
    	}
    )
    
    func TestLoadResources(t *testing.T) {
    	resourcesConfiguration := new(ResourcesConfiguration)
    
    	resourcesLoader := aconfig.LoaderFor(resourcesConfiguration,
    		aconfig.Config{
    			SkipFlags:          true,
    			Files:              []string{"asset_resources.yaml"},
    			FailOnFileNotFound: true,
    			FileDecoders: map[string]aconfig.FileDecoder{
    				".yaml": aconfigyaml.New(),
    			},
    		})
    	if err := resourcesLoader.Load(); err != nil {
    		t.Errorf("failed to load resources configurations [err=%s]", err)
    	}
    
    	t.Logf("%v", *resourcesConfiguration)
    }
    

    Test file: asset_resources.yaml

    resources_a:
    
    resources_b:
    
    

    Panic:

    === RUN   TestLoadResources
    --- FAIL: TestLoadResources (0.00s)
    panic: <nil> <nil> [recovered]
    	panic: <nil> <nil>
    
    goroutine 20 [running]:
    testing.tRunner.func1.2({0x1147b60, 0xc00009e680})
    	/usr/local/go/src/testing/testing.go:1209 +0x24e
    testing.tRunner.func1()
    	/usr/local/go/src/testing/testing.go:1212 +0x218
    panic({0x1147b60, 0xc00009e680})
    	/usr/local/go/src/runtime/panic.go:1038 +0x215
    github.com/cristalhq/aconfig.(*Loader).setFieldData(0x11496c0, 0xc0000ce210, {0x0, 0x0})
    	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:187 +0x9a5
    github.com/cristalhq/aconfig.(*Loader).loadFile(0xc000102000, {0x116b3c9, 0x14})
    	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:330 +0x2f1
    github.com/cristalhq/aconfig.(*Loader).loadFiles(0xc000102000)
    	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:290 +0xea
    github.com/cristalhq/aconfig.(*Loader).loadSources(0xc000102000)
    	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:235 +0x7f
    github.com/cristalhq/aconfig.(*Loader).loadConfig(0xc000102000)
    	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:211 +0x65
    github.com/cristalhq/aconfig.(*Loader).Load(0x1000000)
    	/Users/user/go/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:201 +0x76
    github.com/ic2hrmk/project/subdir/test/config_test.TestLoadResources(0xc000083860)
    	/Users/user/go/src/github.com/ic2hrmk/project/subdir/test/config/load_resources_test.go:37 +0x176
    testing.tRunner(0xc000083860, 0x11741d0)
    	/usr/local/go/src/testing/testing.go:1259 +0x102
    created by testing.(*T).Run
    	/usr/local/go/src/testing/testing.go:1306 +0x35a
    
    

    Expected:

    === RUN   TestLoadResources
        load_resources_test.go:38: {[] []}
    --- PASS: TestLoadResources (0.00s)
    PASS
    

    Thanks!

  • Support for AllowUnknownFlags

    Support for AllowUnknownFlags

    Hi!

    I've been using aconfig for a while now and I love it! Thank you for all the hard work. 😊👍

    I'm trying to load the same flags using different structs in two places in my program and it always complains about the flag not being defined. I've tried using AllowUnknownFlags without success.

    The gist of it is I create a struct with the field A and load it during the initialization of the program, then later on I load a struct with the field B. I then start the program like this: go run main.go --a foo --b bar

    When loading the first struct with only A, it says -b is not defined.

    Any ideas? I'm on the phone now, but can paste a test I built to debug tomorrow if you'd like.

    Thank you again!

  • Panic on map of slices

    Panic on map of slices

    Given the following code:

    var cfg struct {
    	Params url.Values
    }
    os.Setenv("PARAMS", "foo:bar")
    err := aconfig.LoaderFor(&cfg, aconfig.Config{}).Load()
    

    I would expect:

    • either cfg.Params = url.Values{"foo": {"bar"}}
    • or err != nil

    Instead, the program panics with the following traceback:

    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x48 pc=0x4e0fe7]
    
    goroutine 1 [running]:
    github.com/cristalhq/aconfig.(*Loader).setFieldData(0xc0000a2f70, 0xc0000ca160, 0x4f38c0, 0xc00009e710, 0x0, 0x0)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:178 +0x847
    github.com/cristalhq/aconfig.(*Loader).setMap(0xc0000a2f70, 0xc0000ca000, 0xc0000b60b0, 0x7, 0x7, 0xc0000b607a)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:304 +0x439
    github.com/cristalhq/aconfig.(*Loader).setFieldData(0xc0000a2f70, 0xc0000ca000, 0x4f38c0, 0xc00009e6b0, 0xc0000cc098, 0x539b01)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:199 +0xbfe
    github.com/cristalhq/aconfig.(*Loader).setField(0xc0000a2f70, 0xc0000ca000, 0xc0000b607a, 0x6, 0xc0000a0240, 0xc00009ace0, 0xc0000b607a, 0x6)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:433 +0x97
    github.com/cristalhq/aconfig.(*Loader).loadEnvironment(0xc0000a2f70, 0x0, 0x0)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:364 +0x165
    github.com/cristalhq/aconfig.(*Loader).loadSources(0xc0000a2f70, 0xc00009e1f0, 0x0)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:230 +0xf0
    github.com/cristalhq/aconfig.(*Loader).loadConfig(0xc0000a2f70, 0x0, 0xc0000ba020)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:201 +0x4e
    github.com/cristalhq/aconfig.(*Loader).Load(0xc0000a2f70, 0xc0000ba018, 0x0)
    	/home/vasiliy/.cache/gopath/pkg/mod/github.com/cristalhq/[email protected]/aconfig.go:191 +0x2f
    main.main()
    	/home/vasiliy/tmp/toy/toy.go:16 +0xc9
    exit status 2
    

    If the panic is intended behavior, it should be documented.

  • flagSet type is always string

    flagSet type is always string

    I find this module a great alternative to viper when using Cobra, but miss the help output that identifies the type of the flag. Right now, all flags return as string

    type Config struct {
    	Home     string `env:"HOME" required:"true" usage:"Set home"`
    	Port     int64  `default:"1111" usage:"Set int"`
    	WantBool bool   `usage:"Set bool" flag:"wantbool"`
    }
    ...
    Produces:
    Flags:
      -h, --help              help for cli
          --home string       Set home
          --port string       Set int (default "1111")
          --wantbool string   Set bool
    
    Want:
    Flags:
      -h, --help          help for cli
          --home string   Set home
          --port int      Set int (default 1111)
          --wantbool      Set bool
    

    In the new-parser branch, parser.go 144 where it says TODO, is this where you'd expect to do something like:?

    switch field.value.Type().Kind() {
    	case reflect.Bool:
    		boolVal, _ := strconv.ParseBool(field.Tag("default"))
    		l.flagSet.Bool(flagName, boolVal, field.Tag("usage"))
            ...
    }
    

    It's something I'd like to do but am not sure if you'll be merging new-parser in to main.

  • feature: show environment variable names in usage

    feature: show environment variable names in usage

    It would be handy to have some way to print environment variable names either in the -h usage or from some other help flag. This would help make it more obvious to someone running an application configured by aconfig what their config options are.

  • Feature Request: support any type that implements Setter (or any other similar) interface

    Feature Request: support any type that implements Setter (or any other similar) interface

    Hello, thank you for a great library!

    Sometimes there is a need to pass as an ENV variable, not a single value, but a whole struct to override all the fields inside the struct at once. This might probably be needed in highly configurable applications, where the number of exposed config params is big enough. To solve this issue generically, the lib can give developers the ability to set a custom Unmarshaler/Setter for a type.

    A typical interface to support this feature might look like this:

    type Setter interface {
    	SetValue(string) error
    }
    

    Examples from other libs:

    • https://github.com/kelseyhightower/envconfig#custom-decoders
    • https://github.com/ilyakaznacheev/cleanenv#supported-types

    Also, please add a README.md section with information about supported data types/struct tags. Like in the: https://github.com/kelseyhightower/envconfig#supported-struct-field-types

  • YAML arrays can not be mapped to []string

    YAML arrays can not be mapped to []string

    YAML:

    WriteRoles: 
      - Writer
    ReadRoles:
      - Writer
      - Reader
    ListRoles:
      - Writer
      - Reader
    

    Struct:

    type TestConfig struct {
    	ReadRoles  []Role `yaml:"ReadRoles"`
    	WriteRoles []Role `yaml:"WriteRoles"`
    	ListRoles  []Role `yaml:"ListRoles"`
    }
    type Role string
    

    Error:

        suite.go:63: test panicked: string Writer
            goroutine 26 [running]:
            runtime/debug.Stack()
            	/usr/local/opt/go/libexec/src/runtime/debug/stack.go:24 +0x7a
            github.com/stretchr/testify/suite.failOnPanic(0xc000545040)
            	/Users/sivanov/go/pkg/mod/github.com/stretchr/[email protected]/suite/suite.go:63 +0x54
            panic({0x255a9c0, 0xc000088130})
            	/usr/local/opt/go/libexec/src/runtime/panic.go:844 +0x25a
            github.com/cristalhq/aconfig.mii({0x255a9c0, 0xc000191a50})
            	/Users/sivanov/go/pkg/mod/github.com/cristalhq/[email protected]/reflection.go:389 +0x36e
    

    seems that array is just unexpected here: image

  • Panic when parse toml slice

    Panic when parse toml slice

    POC:

    func main() {
    	type MyConfig struct {
    		Slice []struct {
    			A int
    		} `json:"slice" toml:"slice"`
    	}
    
    	var cfg MyConfig
    	loader := aconfig.LoaderFor(&cfg, aconfig.Config{
    		// feel free to skip some steps :)
    		// SkipDefaults: true,
    		// SkipFiles:    true,
    		// SkipEnv:      true,
    		// SkipFlags:    true,
    		EnvPrefix:  "APP",
    		FlagPrefix: "app",
    		Files:      []string{"config.toml"},
    		FileDecoders: map[string]aconfig.FileDecoder{
    			// from `aconfigyaml` submodule
    			// see submodules in repo for more formats
    			".toml": aconfigtoml.New(),
    		},
    	})
    
    	if err := loader.Load(); err != nil {
    		panic(err)
    	}
    	fmt.Printf("%+v\n", cfg)
    }
    

    config.toml file:

    [[slice]]
    A = 5
    

    Result:

    panic: []map[string]interface {} [map[A:5]]
    github.com/cristalhq/[email protected]/reflection.go:190 
    
Jul 4, 2022
Golang config.yaml loader

Description goconfig is a configuration library designed using the following pri

May 31, 2022
⚙️ Dead Simple Config Management, load and persist config without having to think about where and how.

Configo Dead Simple Config Management, load and persist config without having to think about where and how. Install go get github.com/UltiRequiem/conf

Apr 6, 2022
Go-config - Config parser for go that supports environment vars and multiple yaml files

go-multiconfig This package is able to parse yaml config files. It supports gett

Jun 23, 2022
A local LKM rootkit loader/dropper that lists available security mechanisms
A local LKM rootkit loader/dropper that lists available security mechanisms

A local LKM rootkit loader Introduction This loader can list both user and kernel mode protections that are present on the system, and additionally di

Dec 12, 2022
Go C-based plugins loader

dlplugin This package is based on the official Go plugin package, but modified to use any dynamic C libraries (Only Linux, FreeBSD, and macOS). It pro

Sep 6, 2022
A Go (golang) environment loader (which loads env vars from a .env file)

A Go (golang) environment loader (which loads env vars from a .env file)

Feb 8, 2022
INI Loader written in Go

go-ini INI Loader written in Go Single threaded & simple Examples Read all params func (app MyApp) onParam(name string, value string) bool { app.c

Feb 11, 2022
Go-based Docker App Loader

go-loader Go-based Docker App Loader Auto-runs uploaded builds with a Docker Container Structures / Home Page /ping Check Docker Container and show st

Feb 11, 2022
Quick and easy way to load config files based on a simple set of rules.
Quick and easy way to load config files based on a simple set of rules.

config Quick and easy way to load config files based on a simple set of rules. Project inspired by https://github.com/lorenwest/node-config Important

Apr 9, 2021
An opinionated configuration loading framework for Containerized and Cloud-Native applications.
An opinionated configuration loading framework for Containerized and Cloud-Native applications.

Opinionated configuration loading framework for Containerized and 12-Factor compliant applications. Read configurations from Environment Variables, an

Dec 16, 2022
Simple Config Format for Golang.

IndentText Simple Configuration Format that tries to be easy to use and understand at a glance. Unlike other formats, IndentText does not have any typ

Nov 26, 2021
Composable, observable and performant config handling for Go for the distributed processing era

Konfig Composable, observable and performant config handling for Go. Written for larger distributed systems where you may have plenty of configuration

Dec 11, 2022
Sidecar to watch a config folder and reload a process when it changes

A small (3MB uncompressed docker image), efficient (via inotify) sidecar to trigger application reloads when configuration changes.

Dec 29, 2022
Viper wrapper with config inheritance and key generation

piper - Simple Wrapper For Viper Single Source of Truth Generated Key Structs, No Typo Config Inheritance Multiple Config Strategies Support Cache For

Sep 26, 2022
Gonfigure - Read and write config files in go

Gonfigure Reads ini files in golang. Reading INI Files Load file File can be loa

Jan 27, 2022
A lightweight yet powerful config package for Go projects

Config GoLobby Config is a lightweight yet powerful config package for Go projects. It takes advantage of env files and OS variables alongside config

Dec 11, 2022
go implementation of lightbend's HOCON configuration library https://github.com/lightbend/config

HOCON (Human-Optimized Config Object Notation) Configuration library for working with the Lightbend's HOCON format. HOCON is a human-friendly JSON sup

Dec 3, 2022