konf

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Feb 20, 2024 License: MIT Imports: 14 Imported by: 3

README

A minimalist configuration API for Go

Go Version Go Reference Mentioned in Awesome Go Go Report Card Build Coverage

konf offers an(other) opinion on how Go programs can read configuration without becoming coupled to a particular configuration source. It contains two APIs with two different sets of users.

The Config type is intended for application authors. It provides a relatively small API which can be used everywhere you want to read configuration. It defers the actual configuration loading to the Loader interface.

The Loader and Watcher interface is intended for configuration source library implementers. They are pure interfaces which can be implemented to provide the actual configuration.

This decoupling allows application developers to write code in terms of *konf.Config while the configuration source(s) is managed "up stack" (e.g. in or near main()). Application developers can then switch configuration sources(s) as necessary.

Usage

Somewhere, early in an application's life, it will make a decision about which configuration source(s) (implementation) it actually wants to use. Something like:

    //go:embed config
    var config embed.FS

    func main() {
        // Create the Config.
        config := konf.New()

        // Load configuration from embed file system.
        if err := config.Load(fs.New(config, "config/config.json")); err != nil {
            // Handle error here.
        }
        // Load configuration from environment variables.
        if err := config.Load(env.New(env.WithPrefix("server"))); err != nil {
            // Handle error here.
        }

        // Watch the changes of configuration.
        go func() {
          if err := config.Watch(ctx); err != nil {
            // Handle error here.
          }
        }

        konf.SetDefault(config)

        // ... other setup code ...
    }

Outside of this early setup, no other packages need to know about the choice of configuration source(s). They read configuration in terms of functions in package konf:

    func (app *appObject) Run() {
        // Read the server configuration.
        type serverConfig struct {
            Host string
            Port int
        }
        cfg := konf.Get[serverConfig]("server")

        // Register callbacks while server configuration changes.
        konf.OnChange(func() {
          // Reconfig the application object.
        }, "server")

        // ... use cfg in app code ...
    }

Understand the configuration

While the configuration is loaded from multiple sources, static like environments or dynamic like AWS AppConfig, it's hard to understand where a final value comes from. The Config.Explain method provides information about how Config resolve each value from loaders for the given path. One example explanation is like:

config.nest has value [map] is loaded by map.
Here are other value(loader)s:
  - env(env)

Even more, the Config.Explain blurs sensitive information (e.g. password, secret, api keys) by default. It also can customize the blur behavior by providing a custom blur function using config.WithValueFormatter.

Configuration Providers

There are providers for the following configuration sources:

Custom Configuration Providers

You can implement your own provider by implementing the Loader for static configuration loader (e.g fs) or both Loader and Watcher for dynamic configuration loader (e.g. appconfig).

Inspiration

konf is inspired by spf13/viper and knadh/koanf. Thanks for authors of both awesome configuration libraries.

Documentation

Overview

Package konf provides a general-purpose configuration API and abstract interfaces to back that API. Packages in the Go ecosystem can depend on this package, while callers can load configuration from whatever source is appropriate.

It defines a type, Config, which provides a method Config.Unmarshal for loading configuration under the given path into the given object.

Each Config is associated with multiple Loader(s), Which load configuration from a source, such as file, environment variables etc. There is a default Config accessible through top-level functions (such as Unmarshal and Get) that call the corresponding Config methods.

Configuration is hierarchical, and the path is a sequence of keys that separated by delimiter. The default delimiter is `.`, which makes configuration path like `parent.child.key`.

Load Configuration

After creating a Config, you can load configuration from multiple Loader(s) using Config.Load. Each loader takes precedence over the loaders before it. As long as the configuration has been loaded, it can be used in following code to get or unmarshal configuration, even for loading configuration from another source. For example, it can read config file path from environment variables, and then use the file path to load configuration from file system.

Watch Changes

Config.Watch watches and updates configuration when it changes, which leads Config.Unmarshal always returns latest configuration. You may use Config.OnChange to register a callback if the value of any path have been changed. It could push the change into application objects instead pulling the configuration periodically.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CredentialFormatter added in v0.5.0

func CredentialFormatter(_ Loader, path string, value any) string

CredentialFormatter provides the value formatter which blurs sensitive information.

func Get

func Get[T any](path string) T

Get retrieves the value under the given path from the default Config. It returns the zero value of the expected type if there is an error. The path is case-insensitive.

Example
package main

import (
	"embed"
	"fmt"

	"github.com/nil-go/konf"
	"github.com/nil-go/konf/provider/env"

	kfs "github.com/nil-go/konf/provider/fs"
)

func main() {
	ExampleSetDefault()

	fmt.Print(konf.Get[string]("server.host"))
}

//go:embed testdata
var testdata embed.FS

func ExampleSetDefault() {
	config := konf.New()
	if err := config.Load(kfs.New(testdata, "testdata/config.json")); err != nil {

		panic(err)
	}
	if err := config.Load(env.New(env.WithPrefix("server"))); err != nil {

		panic(err)
	}
	konf.SetDefault(config)

}
Output:
example.com

func OnChange

func OnChange(onChange func(), paths ...string)

OnChange registers a callback function that is executed when the value of any given path in the default Config changes. The paths are case-insensitive.

The onChange function must be non-blocking and usually completes instantly. If it requires a long time to complete, it should be executed in a separate goroutine.

This method is concurrency-safe. It panics if onChange is nil.

func SetDefault

func SetDefault(config Config)

SetDefault sets the given Config as the default Config. After this call, the konf package's top functions (e.g. konf.Get) will interact with the given Config.

Example
package main

import (
	"embed"

	"github.com/nil-go/konf"
	"github.com/nil-go/konf/provider/env"

	kfs "github.com/nil-go/konf/provider/fs"
)

//go:embed testdata
var testdata embed.FS

func main() {
	config := konf.New()
	if err := config.Load(kfs.New(testdata, "testdata/config.json")); err != nil {
		// Handle error here.
		panic(err)
	}
	if err := config.Load(env.New(env.WithPrefix("server"))); err != nil {
		// Handle error here.
		panic(err)
	}
	konf.SetDefault(config)
}

func Unmarshal

func Unmarshal(path string, target any) error

Unmarshal reads configuration under the given path from the default Config and decodes it into the given object pointed to by target. The path is case-insensitive.

Example
package main

import (
	"embed"
	"fmt"

	"github.com/nil-go/konf"
	"github.com/nil-go/konf/provider/env"

	kfs "github.com/nil-go/konf/provider/fs"
)

func main() {
	ExampleSetDefault()

	cfg := struct {
		Host string
		Port int
	}{
		Host: "localhost",
		Port: 8080,
	}

	if err := konf.Unmarshal("server", &cfg); err != nil {
		// Handle error here.
		panic(err)
	}
	fmt.Printf("%s:%d\n", cfg.Host, cfg.Port)
}

//go:embed testdata
var testdata embed.FS

func ExampleSetDefault() {
	config := konf.New()
	if err := config.Load(kfs.New(testdata, "testdata/config.json")); err != nil {

		panic(err)
	}
	if err := config.Load(env.New(env.WithPrefix("server"))); err != nil {

		panic(err)
	}
	konf.SetDefault(config)

}
Output:
example.com:8080

Types

type Config

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

Config reads configuration from appropriate sources.

To create a new Config, call New.

func Default

func Default() Config

Default returns the default Config.

func New

func New(opts ...Option) Config

New creates a new Config with the given Option(s).

func (Config) Exists

func (c Config) Exists(path []string) bool

Exists tests if the given path exist in the configuration.

It's used by the loader to check if the configuration has been set by other loaders.

func (Config) Explain

func (c Config) Explain(path string, opts ...ExplainOption) string

Explain provides information about how Config resolve each value from loaders for the given path. The path is case-insensitive.

If there are sensitive information (e.g. password, secret) which should not be exposed, you can use WithValueFormatter to pass a value formatter to blur the information. By default, it uses CredentialFormatter to blur sensitive information.

func (Config) Load

func (c Config) Load(loader Loader) error

Load loads configuration from the given loader. Each loader takes precedence over the loaders before it.

This method can be called multiple times but it is not concurrency-safe. It panics if loader is nil.

func (Config) OnChange

func (c Config) OnChange(onChange func(Config), paths ...string)

OnChange registers a callback function that is executed when the value of any given path in the Config changes. It requires Config.Watch has been called first. The paths are case-insensitive.

The onChange function must be non-blocking and usually completes instantly. If it requires a long time to complete, it should be executed in a separate goroutine.

This method is concurrency-safe. It panics if onChange is nil.

func (Config) Unmarshal

func (c Config) Unmarshal(path string, target any) error

Unmarshal reads configuration under the given path from the Config and decodes it into the given object pointed to by target. The path is case-insensitive.

func (Config) Watch

func (c Config) Watch(ctx context.Context) error

Watch watches and updates configuration when it changes. It blocks until ctx is done, or the service returns an error. WARNING: All loaders passed in Load after calling Watch do not get watched.

It only can be called once. Call after first has no effects. It panics if ctx is nil.

type DecodeHook added in v0.5.0

type DecodeHook any

type ExplainOption

type ExplainOption func(*explainOptions)

ExplainOption configures Config.Explain with specific options.

func WithValueFormatter

func WithValueFormatter(valueFormatter func(Loader, string, any) string) ExplainOption

WithValueFormatter provides the value formatter for Config.Explain. It's for hiding sensitive information (e.g. password, secret) which should not be exposed.

By default, it uses fmt.Sprint to format the value.

type Loader

type Loader interface {
	Load() (map[string]any, error)
}

Loader is the interface that wraps the Load method.

Load loads the latest configuration and returns it as a nested map[string]any. The keys in the returned map should be case-insensitive to avoid random overriding. The keys should be nested like `{parent: {child: {key: 1}}}`.

type Option

type Option func(*options)

Option configures a Config with specific options.

func WithDecodeHook

func WithDecodeHook(decodeHook DecodeHook) Option

WithDecodeHook provides the decode hook for decoding. The decode hook is a function that can transform or customize how values are decoded.

By default, it composes StringToTimeDurationHookFunc, StringToSliceHookFunc(",") and TextUnmarshallerHookFunc.

func WithDelimiter

func WithDelimiter(delimiter string) Option

WithDelimiter provides the delimiter used when specifying config paths. The delimiter is used to separate keys in the path.

For example, with the default delimiter `.`, a config path might look like `parent.child.key`.

func WithLogHandler

func WithLogHandler(handler slog.Handler) Option

WithLogHandler provides the slog.Handler for logs from watch.

By default, it uses handler from slog.Default().

func WithTagName

func WithTagName(tagName string) Option

WithTagName provides the tag name that reads for field names. The tag name is used when decoding configuration into structs.

For example, with the default tag name `konf`, it would look for `konf` tags on struct fields.

type Watcher

type Watcher interface {
	Watch(ctx context.Context, onChange func(map[string]any)) error
}

Watcher is the interface that wraps the Watch method.

Watch watches the configuration and triggers the onChange callback with the latest full configurations as a nested map[string]any when it changes. It blocks until ctx is done, or the watching returns an error.

Directories

Path Synopsis
internal
notifier
azservicebus module
pubsub module
sns module
provider
env
Package env loads configuration from environment variables.
Package env loads configuration from environment variables.
flag
Package flag loads configuration from flags defined by flag.
Package flag loads configuration from flags defined by flag.
fs
Package fs loads configuration from file system.
Package fs loads configuration from file system.
appconfig module
azappconfig module
azblob module
file module
gcs module
pflag module
s3 module
secretmanager module

Jump to

Keyboard shortcuts

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