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

Build Status codecov Go Report Card Go doc

Konfig

Composable, observable and performant config handling for Go. Written for larger distributed systems where you may have plenty of configuration sources - it allows you to compose configurations from multiple sources with reload hooks making it simple to build apps that live in a highly dynamic environment.

What's up with the name?

The name is Swedish for "config". We have a lot of nationalities here at Lalamove and to celebrate cultural diversity most of our open source packages will carry a name derived from a non-English language that is perhaps spoken by at least one of our employees(?).

Why another config package?

Most config packages for Golang are not very extensible and rarely expose interfaces. This makes it complex to build apps which can reload their state dynamically and difficult to mock. Fewer still come with sources such as Vault, Etcd and multiple encoding formats. In short, we didn't find a package that satisfied all of our requirements when we started out.

konfig is built around 4 small interfaces:

  • Loader
  • Watcher
  • Parser
  • Closer

Konfig features include:

  • Dynamic configuration loading
  • Composable load configs from multiple sources, such as vault, files and etcd
  • Polyglot load configs from multiple format. Konfig supports JSON, YAML, TOML, Key=Value
  • Fast, Lock-free, Thread safe Read Reads are up to 10x faster than Viper
  • Observable config - Hot Reload mechanism and tooling to manage state
  • Typed Read get typed values from config or bind a struct
  • Metrics exposed prometheus metrics telling you how many times a config is reloaded, if it failed, and how long it takes to reload

Get started

go get github.com/lalamove/konfig

Load and watch a json formatted config file.

var configFiles = []klfile.File{
	{
		Path:   "./config.json",
		Parser: kpjson.Parser,
	},
}

func init() {
	konfig.Init(konfig.DefaultConfig())
}

func main() {
	// load from json file
	konfig.RegisterLoaderWatcher(
		klfile.New(&klfile.Config{
			Files: configFiles,
			Watch: true,
		}),
		// optionally you can pass config hooks to run when a file is changed
		func(c konfig.Store) error {
			return nil
		},
	)

	if err := konfig.LoadWatch(); err != nil {
		log.Fatal(err)
	}

	// retrieve value from config file
	konfig.Bool("debug")
}

Store

The Store is the base of the config package. It holds and gives access to values stored by keys.

Creating a Store

You can create a global Store by calling konfig.Init(*konfig.Config):

konfig.Init(konfig.DefaultConfig())

The global store is accessible directly from the package:

konfig.Get("foo") // calls store.Get("foo")

You can create a new store by calling konfig.New(*konfig.Config):

s := konfig.New(konfig.DefaultConfig())

Loading and Watching a Store

After registering Loaders and Watchers in the konfig.Store, you must load and watch the store.

You can do both by calling LoadWatch:

if err := konfig.LoadWatch(); err != nil {
	log.Fatal(err)
}

You can call Load only, it will load all loaders and return:

if err := konfig.Load(); err != nil {
	log.Fatal(err)
}

And finally you can call Watch only, it will start all watchers and return:

if err := konfig.Watch(); err != nil {
	log.Fatal(err)
}

Loaders

Loaders load config values into the store. A loader is an implementation of the loader interface.

type Loader interface {
	// Name return the name of the load, it is used to create labeled vectors metrics per loader
	Name() string
	// StopOnFailure indicates if a failure of the loader should trigger a stop
	StopOnFailure() bool
	// Loads the config and add it to the Store
	Load(Store) error
	// MaxRetry returns the maximum number of times to allow retrying on load failure
	MaxRetry() int
	// RetryDelay returns the delay to wait before retrying
	RetryDelay() time.Duration
}

You can register loaders in the config individually or with a watcher.

Register a loader by itself:

configLoader := konfig.RegisterLoader(
	klfile.New(
		&klfile.Config{
			Files: []klfile.File{
				{
					Parser: kpjson.Parser,
					Path:   "./konfig.json",
				},
			},
		},
	),
)

Register a loader with a watcher:

To register a loader and a watcher together, you must register a LoaderWatcher which is an interface that implements both the Loader and the Watcher interface.

configLoader := konfig.RegisterLoaderWatcher(
	klfile.New(
		&klfile.Config{
			Files: []klfile.File{
				{
					Parser: kpjson.Parser,
					Path:   "./konfig.json",
				},
			},
			Watch: true,
		},
	),
)

You can also compose a loader and a watcher to create a LoaderWatcher:

configLoader := konfig.RegisterLoaderWatcher(
	// it creates a LoaderWatcher from a loader and a watcher
	konfig.NewLoaderWatcher(
		someLoader,
		someWatcher,
	),
)

Built in loaders

Konfig already has the following loaders, they all have a built in watcher:

Loads configs from files which can be watched. Files can have different parsers to load different formats. It has a built in file watcher which triggers a config reload (running hooks) when files are modified.

Loads configs from vault secrets. It has a built in Poll Watcher which triggers a config reload (running hooks) before the secret and the token from the auth provider expires.

Loads configs from HTTP sources. Sources can have different parsers to load different formats. It has a built in Poll Diff Watcher which triggers a config reload (running hooks) if data is different.

Loads configs from Etcd keys. Keys can have different parser to load different formats. It has a built in Poll Diff Watcher which triggers a config reload (running hooks) if data is different.

Loads configs from Consul KV. Keys can have different parser to load different formats. It has built in Poll Diff Watcher which triggers a config reload (running hooks) if data is different.

Loads configs from environment variables.

Loads configs from command line flags.

Loads configs from an io.Reader.

Parsers

Parsers parse an io.Reader into a konfig.Store. These are used by some loaders to parse the data they fetch into the config store. The File Loader, Etcd Loader and HTTP Loader use Parsers.

Config already has the following parsers:

Watchers

Watchers trigger a call on a Loader on events. A watcher is an implementation of the Watcher interface.

type Watcher interface {
	// Start starts the watcher, it must not be blocking.
	Start() error
	// Done indicate whether the watcher is done or not
	Done() <-chan struct{}
	// Watch should block until an event unlocks it
	Watch() <-chan struct{}
	// Close closes the watcher, it returns a non nil error if it is already closed
	// or something prevents it from closing properly.
	Close() error
	// Err returns the error attached to the watcher
	Err() error
}

Built in watchers

Konfig already has the following watchers:

Watches files for changes.

Sends events at a given rate, or if diff is enabled. It takes a Getter and fetches the data at a given rate. If data is different, it sends an event.

Hooks

Hooks are functions ran after a successful loader Load() call. They are used to reload the state of the application on a config change.

Registering a loader with some hooks

You can register a loader or a loader watcher with hooks.

configLoader := konfig.RegisterLoaderWatcher(
	klfile.New(
		&klfile.Config{
			Files: []klfile.File{
				{
					Parser: kpyaml.Parser,
					Path:   "./konfig.yaml",
				},
			},
			Watch: true,
		},
	),
	func(s konfig.Store) error {
		// Here you should reload the state of your app
		return nil
	},
)

Adding hooks to an existing loader

You can register a Loader or a LoaderWatcher with hooks.

configLoader.AddHooks(
	func(s konfig.Store) error {
		// Here you should reload the state of your app
		return nil
	},
	func(s konfig.Store) error {
		// Here you should reload the state of your app
		return nil
	},
)

Adding hooks on keys

Alternatively, you can add hooks on keys. Hooks on keys will match for prefix in order to run a hook when any key with a given prefix is updated. A hook can only be run once per load event, even if multiple keys match that hook.

konfig.RegisterKeyHook(
	"db.",
	func(s konfig.Store) error {
		return nil
	},
)

Closers

Closers can be added to konfig so that if konfig fails to load, it will execute Close() on the registered Closers.

type Closer interface {
	Close() error
}

Register a Closer

konfig.RegisterCloser(closer)

Config Groups

You can namespace your configs using config Groups.

konfig.Group("db").RegisterLoaderWatcher(
	klfile.New(
		&klfile.Config{
			Files: []klfile.File{
				{
					Parser: kpyaml.Parser,
					Path:   "./db.yaml",
				},
			},
			Watch: true,
		},
	),
)

// accessing grouped config
dbHost := konfig.Group("db").MustString("credentials.host")

Binding a Type to a Store

You can bind a type to the konfig store if you want your config values to be unmarshaled to a struct or a map[string]interface{}. Then you can access an instance of that type in a thread safe manner (in order to be safe for dynamic config updates).

Let's see with an example of a json config file:

{
    "addr": ":8080",
    "debug": true,
    "db": {
        "username": "foo"
    },
    "redis": {
        "host": "127.0.0.1"
    }
}
type DBConfig struct {
	Username string
}
type Config struct {
	Addr      string
	Debug     string
	DB        DBConfig `konfig:"db"`
	RedisHost string   `konfig:"redis.host"`
}

// we init the root konfig store
konfig.Init(konfig.DefaultConfig())

// we bind the Config struct to the konfig.Store
konfig.Bind(Config{})

// we register our config file
konfig.RegisterLoaderWatcher(
	klfile.New(
		&klfile.Config{
			Files: []klfile.File{
				{
					Parser: kpjson.Parser,
					Path:   "./config.json",
				},
			},
			Watch: true,
		},
	),
)

// we load our config and start watching
if err := konfig.LoadWatch(); err != nil {
	log.Fatal(err)
}

// Get our config value
c := konfig.Value().(Config)

fmt.Println(c.Addr) // :8080

Note that you can compose your config sources. For example, have your credentials come from Vault and be renewed often and have the rest of your config loaded from a file and be updated on file change.

It is important to understand how Konfig unmarshals your config values into your struct. When a Loader calls konfig.Set(), if the konfig store has a value bound to it, it will try to unmarshal the key to the bound value.

  • First, it will look for field tags in the struct, if a tag matches exactly the key, it will unmarshal the key to the struct field.
  • Then, it will do a EqualFold on the field name and the key, if they match, it will unmarshal the key to the struct field.
  • Then, if the key has a dot, it will check if the tag or the field name (to lowercase) is a prefix of the key, if yes, it will check if the type of the field is a struct of pointer, if yes, it will check the struct using what's after the prefix as the key.

Read from config

Apart from reading from the bound config value, konfig provides several methods to read values.

Every method to retrieve config values come in 2 flavours:

  • Get reads a value at the given key. If key is not present it returns the zero value of the type.
  • MustGet reads a value at the given key. If key is not present it panics.

All methods to read values from a Store:

// Exists checks whether the key k is set in the store.
Exists(k string) bool

// Get gets the value with the key k from the store. If the key is not set, Get returns nil. To check whether a value is really set, use Exists.
Get(k string) interface{}
// MustGet tries to get the value with the key k from the store. If the key k does not exist in the store, MustGet panics.
MustGet(k string) interface{}

// MustString tries to get the value with the key k from the store and casts it to a string. If the key k does not exist in the store, MustString panics.
MustString(k string) string
// String tries to get the value with the key k from the store and casts it to a string. If the key k does not exist it returns the Zero value.
String(k string) string

// MustInt tries to get the value with the key k from the store and casts it to a int. If the key k does not exist in the store, MustInt panics.
MustInt(k string) int
// Int tries to get the value with the key k from the store and casts it to a int. If the key k does not exist it returns the Zero value.
Int(k string) int

// MustFloat tries to get the value with the key k from the store and casts it to a float. If the key k does not exist in the store, MustFloat panics.
MustFloat(k string) float64
// Float tries to get the value with the key k from the store and casts it to a float. If the key k does not exist it returns the Zero value.
Float(k string) float64

// MustBool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist in the store, MustBool panics.
MustBool(k string) bool
// Bool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist it returns the Zero value.
Bool(k string) bool

// MustDuration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist in the store, MustDuration panics.
MustDuration(k string) time.Duration
// Duration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist it returns the Zero value.
Duration(k string) time.Duration

// MustTime tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist in the store, MustTime panics.
MustTime(k string) time.Time
// Time tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist it returns the Zero value.
Time(k string) time.Time

// MustStringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist in the store, MustStringSlice panics.
MustStringSlice(k string) []string
// StringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist it returns the Zero value.
StringSlice(k string) []string

// MustIntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist in the store, MustIntSlice panics.
MustIntSlice(k string) []int
// IntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist it returns the Zero value.
IntSlice(k string) []int

// MustStringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist in the store, MustStringMap panics.
MustStringMap(k string) map[string]interface{}
// StringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist it returns the Zero value.
StringMap(k string) map[string]interface{}

// MustStringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist in the store, MustStringMapString panics.
MustStringMapString(k string) map[string]string
// StringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist it returns the Zero value.
StringMapString(k string) map[string]string

Strict Keys

You can define required keys on the konfig.Store by calling the Strict method. When calling strict method, konfig will set required keys on the store and during the first Load call on the store it will check if the keys are present, if not, Load will return a non nil error. Then, after every Load on a loader, konfig will check again if the keys are still present, if not, the loader Load will be considered a failure.

Usage:

// We init the root konfig store
konfig.Init(konfig.DefaultConfig()).Strict("debug", "username")

// Register our loaders
...

// We load our config and start watching.
// If strict keys are not found after the load operation, LoadWatch will return a non nil error.
if err := konfig.LoadWatch(); err != nil {
	log.Fatal(err)
}

Alternatively, BindStructStrict can be used to strictly bind config. Usage:

type DBConfig struct {
	Username string
}
type Config struct {
	Addr      string    `konfig:"-"` // this key will be non-strict
	DB        DBConfig  `konfig:"db"`
	RedisHost string    `konfig:"redis.host"`
}

// we init the root konfig store
konfig.Init(konfig.DefaultConfig())

// we bind the Config struct to the konfig.Store
konfig.BindStructStrict(Config{})

// Register our loaders
...

// We load our config and start watching.
// If any strict key is not found after the load operation, LoadWatch will return a non nil error.
if err := konfig.LoadWatch(); err != nil {
	log.Fatal(err)
}

Getter

To easily build services which can use dynamically loaded configs you can create getters for specific keys. A getter implements ngetter.GetterTyped from nui package. It is useful when building apps in larger distributed environments.

Example with a config value set for the debug key:

debug := konfig.Getter("debug")

debug.Bool() // true

Metrics

Konfig comes with prometheus metrics.

Two metrics are exposed:

  • Config reloads counter vector with labels
  • Config reload duration summary vector with labels

Example of metrics:

# HELP konfig_loader_reload Number of config loader reload
# TYPE konfig_loader_reload counter
konfig_loader_reload{loader="config-files",result="failure",store="root"} 0.0
konfig_loader_reload{loader="config-files",result="success",store="root"} 1.0

# HELP konfig_loader_reload_duration Histogram for the config reload duration
# TYPE konfig_loader_reload_duration summary
konfig_loader_reload_duration{loader="config-files",store="root",quantile="0.5"} 0.001227641
konfig_loader_reload_duration{loader="config-files",store="root",quantile="0.9"} 0.001227641
konfig_loader_reload_duration{loader="config-files",store="root",quantile="0.99"} 0.001227641
konfig_loader_reload_duration_sum{loader="config-files",store=""} 0.001227641
konfig_loader_reload_duration_count{loader="config-files",store=""} 1.0

To enable metrics, you must pass a custom config when creating a config store:

konfig.Init(&konfig.Config{
	Metrics: true,
	Name: "root",
})

Benchmark

Benchmarks are run on viper, go-config and konfig. Benchmark are done on reading ops and show that Konfig is 0 allocs on read and at leat 3x faster than Viper:

cd benchmarks && go test -bench . && cd ../
goos: linux
goarch: amd64
pkg: github.com/lalamove/konfig/benchmarks
BenchmarkGetKonfig-4            200000000                7.75 ns/op            0 B/op          0 allocs/op
BenchmarkStringKonfig-4         30000000                49.9 ns/op             0 B/op          0 allocs/op
BenchmarkGetViper-4             20000000               101 ns/op              32 B/op          2 allocs/op
BenchmarkStringViper-4          10000000               152 ns/op              32 B/op          2 allocs/op
BenchmarkGetGoConfig-4          10000000               118 ns/op              40 B/op          3 allocs/op
BenchmarkStringGoConfig-4       10000000               125 ns/op              40 B/op          3 allocs/op
PASS

Contributing

Contributions are welcome. To make contributions, fork the repository, create a branch and submit a Pull Request to the master branch.

Comments
  • Supporting Versioned Vault KV Version 2

    Supporting Versioned Vault KV Version 2

    As per #47

    • I have added support for Vault KV Version 2 (for supporting secret paths like /secret/data/mysecret) according to the Vault API for KV Version 2, by default this will return latest version of the secret.

    • This POC also adds support for requesting different versions of a particular secret by defining the key like /secret/data/mysecret?version=2

    • For adding support for KV Version 2 I had to use ReadWithData, this opens up passing additional parameters to Vault API for example /aws/sts/my-role?ttl=45m

    I have tested this change with Vault and tested for backwards compatibility. I can also add more documentation of this change if this MR moves forward.

  • Bugfix for Reading KV Version 2 Values

    Bugfix for Reading KV Version 2 Values

    First of all sorry for missing the condition in #49 While testing KV V2 support on an actual project I encountered a bug coming in from https://github.com/lalamove/konfig/blob/master/loader/klvault/vaultloader.go#L180 which causes an panic as the full key path is being passed instead of the parsed value. This pull request has the fix for the bug and I have also checked in my test repo to reproduce this error and serve as test when this patch is applied. The repo can be found here: https://github.com/shreyu86/konfig-test .

    Once again apologies for the bug, I should have performed an integration test sooner.

  • Feature Request: Support Vault KV Version 2

    Feature Request: Support Vault KV Version 2

  • General functionality of konfig

    General functionality of konfig

    When I ran into konfig, I really liked your approach and extensibility. I started writing an extension for support of consul, fixed couple of bugs and was almost ready to submit a pull request, however I ran into more philosophical issue and wanted to check with you to see what's your thinking.

    You build konfig to behave like first class citizen within other people code. That means you terminate code if significant issues are found with the integrity of your package, which is fine. However you also completely stop processing if you run into any kind of integrity errors within a config itself.

    This in general is fine for local configs and debugging, but poses major issues for all remote configs. You see, people are sloppy. Just a simple typo in a yaml config will take down the whole parser which in turns stops konfig from functioning.

    Yes, you added some helpers like NoExitOnError but this will only let the program to continue working without letting it to retry to pull a remote config while wrecking a havoc in the logs.

    My general strategy why using remote configs is:

    • add local config that is used up until new remote config is not provided
    • overwrite the default config with remote config if found

    By stoping the whole process means that no new configs will be pulled (without informing the user). Imagine running 10,000 servers in k8 and because of a typo, none of them can pull a config from etcd. Your only solution is to restart/rebuild the whole cluster.

    My suggestion would be to rebuild the logic in a way that it will not exhibit this behavior. I'm happy to chip in. Otherwise I can submit what I have, but I think that will be for me.

  • Feature Request: Allow marking all struct field as strict

    Feature Request: Allow marking all struct field as strict

    Hi, thanks for the wonderful library.

    Currently I bind my Store to a configuration struct and I have a yaml file that should always have defaults for all flags which can then be overriden with env parameters.

    I think if we could add something like a BindStrict that takes in a struct and adds all the exposed fields as strict keys would allow not needing to keep a separate list of canonicalised keys to be passed manually to the StrictKey function.

  • klfile: remove defer drom the loop

    klfile: remove defer drom the loop

    Hi! There is better not use defer inside the loop, because defer guaranteed to executed at the end of the function. defer works as a stack, so, in your loop, fd.Close() will be pushing to the stack and at the end of the loop it'll be executing at reverse order. Check a little snippet In this PR i've just moved fd.Close at the end of the loop and to the error handling. But probably you want to handle it on the different way(each file on the loop read at the goroutine, or so)

  • Issue with github.com/lucas-clemente/quic-go@v0.12.0 (indirect via github.com/micro/go-micro@v1.10.0)

    Issue with github.com/lucas-clemente/[email protected] (indirect via github.com/micro/[email protected])

    The v0.12.0 tag for github.com/lucas-clemente/quic-go is broken (invalid signature, as discussed in https://github.com/lucas-clemente/quic-go/issues/2163).

    Since github.com/lucas-clemente/quic-go is imported via go-micro (now Nitro), this is the package to bump.

    A quick check shows that bumping go-micro to v1.12.0 should solve the issue.

  • Allow to pass a custom login path for vault loader

    Allow to pass a custom login path for vault loader

    We use a custom mount point for authentication and the working vault client config looks like:

    cache {
        use_auto_auth_token = true
    }
    
    listener "tcp" {
        address = "127.0.0.1:8200"
        tls_disable = true
    }
    
    auto_auth {
        method "kubernetes" {
            mount_path = "auth/k8s_dev"
            config = {
                role = "testnamespace-dev"
            }
        }
    
        sink "file" {
            config = {
                path = "/vault/token/vault-token"
            }
        }
    }
    
    vault {
        address = "https://vaul.com:8200"
    }
    
  • Go modules dependency fetching doesn't work

    Go modules dependency fetching doesn't work

    1:

    >> go version
    go version go1.14.1 linux/amd64
    

    2:

    >> git clone https://github.com/lalamove/konfig && cd konfig
    

    3:

    >> go mod download
    go: contrib.go.opencensus.io/exporter/[email protected] requires
    	github.com/census-instrumentation/[email protected]: invalid pseudo-version: version before v0.1.0 would have negative patch number
    
  • Add recovery on panics in watcher and add a MaxWatcherPanics setting

    Add recovery on panics in watcher and add a MaxWatcherPanics setting

    • Adding possibility for watchers to recover from panics based on a MaxWatcherPanics setting. By default, it doesn't recover from any panics, so the behaviour does not change unless MaxWatcherPanics is set to a value greater than 0.
    • Adding better logs on panics and errors.
    • Adding godoc package comments.
  • Feature/hooks values

    Feature/hooks values

    This MR adds hooks on values and handling of map[string]struct on bound value.

    • Hooks on values: You can define hooks on a matching keys. It checks for prefix so you can match a range of keys to run a single hook. I was considering regexp but I think it's expensive and I don't think there's a meaningful use case. The syntax to register a key hook is:
    konfig.RegisterKeyHook(k, func(konfig.Store) error {
        return nil
    })
    
    • Handling map[string]struct on bound value: When unmarshaling the config state to the bound structure, we were only handling struct types. Now it can also handle map[string]struct to be able to unmarshal key value lists where the key is unknown.
  • Smaller integers cannot be marshalled into

    Smaller integers cannot be marshalled into

    Here's an example

    type Config struct {
    	Port    uint16
    }
    

    config.yml:

    port: 8080
    
    FILEWATCHER | 2022/06/11 19:46:20 adding file to watch: ./config.yml
    panic: reflect.Set: value of type int is not assignable to type uint16
    
    goroutine 1 [running]:
    reflect.Value.assignTo({0x111ce80?, 0x13a84e0?, 0xc0001a4300?}, {0x1177edc, 0xb}, 0x111d7c0, 0x0)
            C:/Program Files/Go/src/reflect/value.go:3062 +0x2ac
    reflect.Value.Set({0x111d7c0?, 0xc0001a4300?, 0x111ce80?}, {0x111ce80?, 0x13a84e0?, 0x111d7c0?})
            C:/Program Files/Go/src/reflect/value.go:2088 +0xeb
    github.com/lalamove/konfig.(*value).setStruct(0xc00019a3f0, {0xc0001a2600, 0x4}, {0x111ce80, 0x13a84e0}, {0x1113e20?, 0xc0001a4300?, 0x30000?})
            C:/Users/steve/go/pkg/mod/github.com/lalamove/[email protected]/value.go:221 +0x34f
    github.com/lalamove/konfig.(*value).setValues(0xc00019a3f0, 0xc00019a8a0?)
            C:/Users/steve/go/pkg/mod/github.com/lalamove/[email protected]/value.go:196 +0x22f
    github.com/lalamove/konfig.Values.load(0xc00019a510, 0x111b440?, 0xc00019c0b0)
            C:/Users/steve/go/pkg/mod/github.com/lalamove/[email protected]/values.go:61 +0x738
    github.com/lalamove/konfig.(*S).loaderLoadRetry(0xc00019c0b0, 0xc000180060, 0x0)
            C:/Users/steve/go/pkg/mod/github.com/lalamove/[email protected]/loader.go:149 +0x276
    github.com/lalamove/konfig.(*S).Load(0xc00019c0b0)
            C:/Users/steve/go/pkg/mod/github.com/lalamove/[email protected]/loader.go:69 +0x74
    github.com/lalamove/konfig.(*S).LoadWatch(0x1?)
            C:/Users/steve/go/pkg/mod/github.com/lalamove/[email protected]/loader.go:49 +0x1e
    github.com/lalamove/konfig.LoadWatch()
            C:/Users/steve/go/pkg/mod/github.com/lalamove/[email protected]/loader.go:44 +0x1e
    
  • Supported set to pointers

    Supported set to pointers

    Supported assign parsed value not only to value types (string, bool, etcetera) but also to pointer types (*string, *bool, etcetera). It's very useful to distinguish missed in config value from present in config

  • Support for unmarshalling of loaded config

    Support for unmarshalling of loaded config

    In extensible/modular application, it's not possible to create statically defined struct with configuration. And, modular configuration would have for example following configuration:

    # syntax: <module_key>.<options...>
    
    module1.option1 = "..."
    module1.option2.a = "..."
    module1.option2.b = "..."
    module1.option3.m = "..."
    module1.option3.n = "..."
    module1.option3.o = "..."
    
    module2.option1.x = "..."
    module2.option1.y = "..."
    module2.option1.z = "..."
    module2.option2 = "..."
    
    # ...
    

    So:

    type Module interface {
        LoadConfig(s konfig.Store)
    }
    
    type ModuleX struct {}
    
    func (m *ModuleX) LoadConfig(s konfig.Store) {
        var options ModuleXOptions
        s.Unmarshal(&options)
    }
    
    func main() {
        var modules map[string]Module = ...
        var config konfig.Store =  ...
    
        for key, module := range modules {
            // actually, don't know if this works in konfig,... 
            // get all options with prefix `<key>.`
            module.LoadConfig(config.Group(key))
        } 
    }
    

    Somewhat similar functionality is provided by https://github.com/knadh/koanf#unmarshalling. But, it would need a bit different Module interface:

    type Module interface {
        LoadConfig(path string, k *koanf.Koanf)
    }
    
    type ModuleX struct {}
    
    func (m *ModuleX) LoadConfig(path string, k *koanf.Koanf) {
        var options ModuleXOptions
        k.Unmarshal(path, &options)
    }
    
    func main() {
        ....
    }
    

    Are there any plans for such/similar support?

  • Change the order of priority

    Change the order of priority

    Hello,

    Currently having three sources to retrieve values (file, env, flag), I am looking for a way to get the following order of priority (from the lowest to the highest): default (flag) - file - env - flag

    Indeed, since the default values are defined in the FlagSet, they are in priority on file and env sources (so currently the priority order is as follows: file - env - default (flag) - flag)

    Is it possible to achieve the expected result?

    Thank you.

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 flexible and composable configuration library for Go that doesn't suck

croconf A flexible and composable configuration library for Go that doesn't suck Ned's spec for Go configuration which doesn't suck: Fully testable: t

Nov 17, 2022
A flexible and composable configuration library for Go that doesn't suck

croconf A flexible and composable configuration library for Go Why? We know that there are plenty of other Go configuration and CLI libraries out ther

Nov 17, 2022
Simple, useful and opinionated config loader.

aconfig Simple, useful and opinionated config loader. Rationale There are many solutions regarding configuration loading in Go. I was looking for a si

Dec 26, 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
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
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
Jul 4, 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
🔥🔥 🌈 Golang configuration,use to Viper reading from remote Nacos config systems. Viper remote for Naocs.

Viper remote for Nacos Golang configuration,use to Viper reading from remote Nacos config systems. Viper remote for Naocs. runtime_viper := viper.New(

Dec 6, 2022
create a bootable disk image from Docker image or a yaml config

docker2boot docker2boot creates a bootable disk from either a Docker image or a config yaml file Features status dns Y cloud-init Y network Y ssh TODO

Oct 30, 2022
Nacos is a service config & discovery.

Kratos Nacos example sc := []constant.ServerConfig{ *constant.NewServerConfig("127.0.0.1", 8848), } cc := &constant.ClientConfig{ NamespaceId:

Oct 28, 2021
Little Go tool to infer an uncrustify config file from an expected format

uncrustify-infer Little Go tool to infer an uncrustify config file from an expected format Install This tool relies on an uncrustify executable, you m

Oct 8, 2021
Ewwwwwww - Yuck Config
Ewwwwwww - Yuck Config

Eww Config What is it Using Eww Widgets to create a replacement for my bumblebee-status bars in i3 Window Manager. Eww allows you to create widgets an

Dec 29, 2022
A lightweight config center written by golang.

A lightweight config center written by golang.

Jan 21, 2022
Project my config into your prebuild

Projector A simple key value store per path. Building From Source git clone [email protected]:ThePrimeagen/projector.git cd projector # Install it where

Jun 23, 2022