Simple, useful and opinionated config loader.

Overview

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
  • Затруднение при запуске тестов из-под 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 Я что-то делаю не так?

    bug 
    opened by Rid-lin 5
  • 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{""}

    opened by mef13 4
  • 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

    feature 
    opened by Tasssadar 4
  • 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.

    opened by EmilijusS 3
  • 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

    opened by MichaelCurrin 3
  • 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

    feature 
    opened by jakubdyszkiewicz 3
  • 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.

    bug 
    opened by MockyBang 2
  • 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!

    bug 
    opened by ic2hrmk 2
  • 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!

    feature 
    opened by simongottschlag 2
  • 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.

    bug 
    opened by vfaronov 1
  • Error while parsing nested structs in slice in yaml file

    Error while parsing nested structs in slice in yaml file

    There is an error while parsing structs inside a slice. The source is yaml file, but probably will work the same with other types.

    The problem is that the library tries to set string representation of nested struct to a slice (but string representation nested struct looks like "map[field1:a slice_of_struct2:[map[field2:b field3:c]]]") and fails.

    To reproduce an error:

    package main
    
    import (
    	"io/ioutil"
    	"os"
    
    	"github.com/cristalhq/aconfig"
    	"github.com/cristalhq/aconfig/aconfigyaml"
    )
    
    const testStruct = `
    struct1:
      slice_of_struct1:
        - field1: a
          slice_of_struct2:
            - field2: b
              field3: c`
    
    type TestStruct struct {
    	Struct1 struct {
    		SliceOfStruct1 []struct {
    			Field1         string `json:"field1"`
    			SliceOfStruct2 []struct {
    				Field2 string `json:"field2"`
    				Field3 string `json:"field3"`
    			} `yaml:"slice_of_struct2"`
    		} `yaml:"slice_of_struct1"`
    	} `yaml:"struct1"`
    }
    
    func cfgError() {
    
    	tmpfile, err := ioutil.TempFile("", "test.*.yaml")
    	if err != nil {
    		panic(err)
    	}
    
    	defer os.Remove(tmpfile.Name())
    
    	if _, err := tmpfile.Write([]byte(testStruct)); err != nil {
    		panic(err)
    	}
    	if err := tmpfile.Close(); err != nil {
    		panic(err)
    	}
    
    	var cfg TestStruct
    
    	loader := aconfig.LoaderFor(&cfg, aconfig.Config{
    		SkipFlags: true,
    		Files:     []string{tmpfile.Name()},
    		FileDecoders: map[string]aconfig.FileDecoder{
    			".yaml": aconfigyaml.New(),
    		},
    	})
    	if err := loader.Load(); err != nil {
    		panic(err) // aconfig: cannot load config: loading files: incorrect slice item "map[field1:a slice_of_struct2:[map[field2:b field3:c]]]": type kind "struct" isn't supported
    	}
    }
    
    bug 
    opened by ilyakaznacheev 1
  • 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

    documentation feature 
    opened by skovtunenko 1
  • 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

    bug 
    opened by newink 0
  • 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 
    
    bug 
    opened by bynil 3
  • Deprecations

    Deprecations

    It's not common to deprecate fields to remove them in the next release or move them to some other place. It would be nice to have support for it in aconfig. Maybe something like

    type MyConfig struct {
    	Hostname int `default:"xyz.internal" usage:"hostname" deprecated:"this will be gone in the next release, the internet is a dangerous place, localhost will be a default"`
    }
    

    and have a function to collect all deprecations if the value is set.

    feature 
    opened by jakubdyszkiewicz 4
  • Does aconfig support short and long versions of fields?

    Does aconfig support short and long versions of fields?

    I'm trying to find out if it's possible that aconfig is able to parse a field that has 2 different uses, a short version (string) and a long version (struct) which provides more detailed version of the overall use.

    For example

      dependencies:
        - name: example-plugin
          url: github://someuser/somerepo
          branch: master
          release: v1.0.0
    
        - name: plugin-repo-without-package-file
          url: gitlab://someuser/somerepo
          branch: dev
          release:
            name: my_plugin_{{.OS}}
            tag: v2.1.3
            type: archive
            path: plugin/plugin
    

    Would I be able to do this, where I can parse it as an interface and then handle extracting it based on it's type?

    opened by ADRFranklin 4
Releases(v0.18.1)
Owner
cristaltech
cristaltech
Noel 2 Jul 4, 2022
Golang config.yaml loader

Description goconfig is a configuration library designed using the following pri

null 2 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

Eliaz Bobadilla 8 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

Dimitris Tassopoulos 1 Jun 23, 2022
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

RedCode Labs 46 Aug 22, 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

Alexey Nosov 5 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)

Bendt Indonesia 0 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

Denis 1 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

zgur.ETH 0 Feb 11, 2022
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

Tarcisio Gruppi 1 Apr 9, 2021
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

Sherif Abdel-Naby 81 Sep 25, 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

Anthony DeDominic 1 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

Lalamove 631 Sep 20, 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.

Florent Delannoy 23 Sep 7, 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

null 7 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

Ibrahim Shahzad 2 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

GoLobby 293 Sep 28, 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

Gürkan Kaymak 49 Sep 26, 2022