Categorygithub.com/ovh/configstore
modulepackage
0.6.2
Repository: https://github.com/ovh/configstore.git
Documentation: pkg.go.dev

# README

configstore

The configstore library aims to facilitate configuration discovery and management. It mixes configuration items coming from various (abstracted) data sources, called providers.

GoDoc Go Report Card

Items

An item is composed of 3 fields:

  • Key: The name of the item. Does not have to be unique. The provider is responsible for giving a sensible initial value.
  • Value: The content of the item. This can be either manipulated as a plain scalar string, or as a marshaled (JSON or YAML) blob for complex objects.
  • Priority: An abstract integer value to use when priorizing between items sharing the same key. The provider is responsible for giving a sensible initial value.

Configuration format

The item keys are NOT case-sensitive. Also, - and _ characters are equivalent.

The exact input format of the configuration depends on the provider. Providers can either be loaded manually in your code, or controlled by the env variable CONFIGURATION_FROM.

Example main.go

func main() {
    configstore.InitFromEnvironment()

    val, err := configstore.GetItemValue("foo")
    if err != nil {
        panic(err)
    }
    fmt.Println(val)
}

Outputs:

bar

Reading from a file

Env:

CONFIGURATION_FROM=file:foo.cfg

Contents of foo.cfg file:

- key: foo
  priority: 12
  value: bar

Key/value pairs are read from a single file in yaml.

Reading from env

Env:

CONFIGURATION_FROM=env:CONFIG
CONFIG_FOO=bar

Key/value pairs are read from the environment, with an optional prefix. Remember that key names are case-insensitive, and that _ and - are equivalent in key names.

Reading from a file hierarchy

Env:

CONFIGURATION_FROM=filetree:configdir

Contents of configdir directory:

foo

Contents of configdir/foo file:

bar

Key/value pairs are read by traversing a root directory. Each file in the dir represents an item: the filename is the key, the contents are the value. To have several items sharing the same key, you can use a single level of sub-directory as such: configdir/foo/bar1, configdir/foo/bar2, ... The filenames bar1/bar2 are not used in the resulting items.

Reading from a custom source

These built-in providers implement common sources of configuration, but configstore can be expanded with other data sources. See Example: multiple providers.

Example 101

file.txt:

- key: foo
  value: bar
- key: baz
  value: bazz
func main() {
    configstore.File("/path/to/file.txt")
    v, err := configstore.GetItemValue("foo")
    fmt.Println(v, err)
}

This very basic example describes how to get a string out of a configuration file (which can be JSON or YAML). To do more advanced configuration manipulation, see the next examples.

Example: multiple providers

Configuration Providers represent an abstract data source. Their only role is to return a list of items.

Some built-in implementations are available (in-memory, file, env, ...), but the library exposes a way to register a provider factory, to extend it and bridge with any other existing system.

Example mixing several providers

// custom provider with hardcoded values
func MyProviderFunc() (configstore.ItemList, error) {
    ret := configstore.ItemList{
        Items: []configstore.Item{
            // an item has 3 components: key, value, priority
            // they are defined by the provider, but can be modified later by the library user
            configstore.NewItem("key1", `value1-higher-prio`, 6),
            configstore.NewItem("key1", `value1-lower-prio`, 5),
            configstore.NewItem("key2", `value2`, 5),
        },
    }
    return ret, nil
}

func main() {

    configstore.RegisterProvider("myprovider", MyProviderFunc)
    configstore.File("/path/to/file.txt")
    configstore.Env("CONFIG_")

    // blends items from all sources
    items, err := configstore.GetItemList()
    if err != nil {
        panic(err)
    }

    for _, i := range items.Items {
        val, err := i.Value()
        if err != nil {
            panic(err)
        }
        fmt.Println(i.Key(), val, i.Priority())
    }
}

Example: advanced filtering

When calling configstore.GetItemList(), the caller gets an ItemList.

This object contains all the configuration items. To manipulate it, you can use a ItemFilter object, which provides convenient helper functions to select and reorder the items.

All objects are safe to use even when the item list is empty.

Assuming the following configuration file:

- key: database
  value: '{"name": "foo", "ip": "192.168.0.1", "port": 5432, "type": "RO"}'
- key: database
  value: '{"name": "foo", "ip": "192.168.0.1", "port": 5433, "type": "RW"}'
- key: database
  value: '{"name": "bar", "ip": "192.168.0.1", "port": 5434, "type": "RO"}'
- key: other
  value: misc

Our program wants to retrieve database credentials, favoring RW over RO when both are present for the same database:

func main() {

    configstore.File("example.txt")

    items, err := configstore.GetItemList()
    if err != nil {
        panic(err)
    }

    // we start by building a filter to manipulate our configuration items
    // we will apply it on our items list later
    filter := configstore.Filter()

    // extract only the "database" items
    filter = filter.Slice("database")

    // now we have a list of database objects, with 3 items:
    // {"name": "foo", "ip": "192.168.0.1", "port": 5432, "type": "RO"}
    // {"name": "foo", "ip": "192.168.0.1", "port": 5433, "type": "RW"}
    // {"name": "bar", "ip": "192.168.0.1", "port": 5434, "type": "RO"}
    //
    // the "database" key provides too little information to further classify items
    // we need to know the database name and type to further regroup and prioritize them
    // for that, we need to drill down into the actual item value

    // we need to unmarshal the JSON representation of every item
    // we pass a factory function that instantiates objects of the correct concrete type
    // it will be called for each item in the sublist, and each item is then unmarshaled (JSON or YAML) into the returned object
    filter = filter.Unmarshal(func() interface{} { return &Database{} })

    // now we want to actually index and lookup by database name, instead of the generic "database" key
    // we apply a rekey function that does payload inspection and renames each item
    //
    // our rekey function was written with knowledge of the objects being manipulated,
    // and uses the unmarshaled objects, not the raw text
    filter = filter.Rekey(rekeyByName)

    // we have redundant items: database "foo" is present twice (RO and RW)
    // we want to favor the RW instance if possible
    // we apply a reordering function that re-assigns item priorities
    // after inspecting the unmarshaled objects
    filter = filter.Reorder(prioritizeRW)

    // we only need 1 of each distinct database
    // items relating to the same database now share the same key,
    // and their priority properly reflects whether they are more or less important (RO or RW)
    // we apply a squash to keep only the items with the single highest priority value, for each key
    // = RW items of each database if available, RO otherwise
    filter = filter.Squash()

    // now we have only 2 items left:
    // {"name": "foo", "ip": "192.168.0.1", "port": 5433, "type": "RW"}
    // {"name": "bar", "ip": "192.168.0.1", "port": 5434, "type": "RO"}

    // we can finally apply it on our list
    items = filter.Apply(items)

    // all these transformations can be chained as a one-liner description of the filter steps:
    filter = configstore.Filter().Slice("database").Unmarshal(func() interface{} { return &Database{} }).Rekey(rekeyByName).Reorder(prioritizeRW).Squash()
    items, err = filter.GetItemList() // shortcut: applies the filter to the full list from configstore.GetItemList()
    if err != nil {
        panic(err)
    }

    // declaring your filter separately like this lets you define it globally and execute it later
    // that way, you can use its description (String()) to generate usage information.
    //
    // in this example, filter.String() would output:
    // database: {"name": "", "ip": "", "port": "", "type": ""}
}

type Database struct {
    Name string `json:"name"`
    IP   string `json:"ip"`
    Port int    `json:"port"`
    Type string `json:"type"`
}

func rekeyByName(s *configstore.Item) string {
    i, err := s.Unmarshaled()
    // we see here the error that was produced when we called *ItemList.Unmarshal(...)*
    // we ignore it for now, it will be handled when the *main()* retrieves the object.
    if err == nil {
        return i.(*Database).Name
    }
    return s.Key()
}

func prioritizeRW(s *configstore.Item) int64 {
    i, err := s.Unmarshaled()
    if err == nil {
        if i.(*Database).Type == "RW" {
            return s.Priority() + 1
        }
    }
    return s.Priority()
}

# Functions

AllowProviderOverride allows multiple calls to RegisterProvider() with the same provider name.
Env registers a provider reading from the environment.
ErrorProvider registers a configstore provider which always returns an error.
File registers a configstore provider which reads from the file given in parameter (static content).
FileCustom registers a configstore provider which reads from the file given in parameter, and loads the content using the given unmarshal function.
FileCustomRefresh registers a configstore provider which reads from the file given in parameter, and loads the content using the given unmarshal function; and watches file stat for auto refresh.
FileList registers a configstore provider which reads from the files contained in the directory given in parameter.
FileListRefresh is similar to the FileList provider with the refresh feature enabled.
FileRefresh registers a configstore provider which readfs from the file given in parameter (provider watches file stat for auto refresh, watchers get notified).
FileTree registers a configstore provider which reads from the files contained in the directory given in parameter.
FileTreeRefresh is similar to the FileTree provider with the refresh feature enabled.
Filter creates a new empty filter object.
GetItem retrieves the full item list, merging the results from all providers, then returns a single item by key.
GetItemList retrieves the full item list, merging the results from all providers.
GetItemValue fetches the full item list, merging the results from all providers, then returns a single item's value by key.
GetItemValueBool fetches the full item list, merging the results from all providers, then returns a single item's value by key.
GetItemValueDuration fetches the full item list, merging the results from all providers, then returns a single item's value by key.
GetItemValueFloat fetches the full item list, merging the results from all providers, then returns a single item's value by key.
GetItemValueInt fetches the full item list, merging the results from all providers, then returns a single item's value by key.
GetItemValueUint fetches the full item list, merging the results from all providers, then returns a single item's value by key.
InitFromEnvironment initializes configuration providers via their name and an optional argument.
InMemory registers an InMemoryProvider with a given arbitrary name and returns it.
NewItem creates a item object from key / value / priority values.
No description provided by the author
NotifyIsMuted reports whether notifications are currently muted.
NotifyMute prevents configstore from notifying watchers on configuration changes, until MotifyUnmute() is called.
NotifyUnmute allows configstore to resume notifications to watchers on configuration changes.
NotifyWatchers is used by providers to notify of configuration changes.
RegisterProvider registers a provider.
RegisterProviderFactory registers a factory function so that InitFromEnvironment can properly instantiate configuration providers via name + argument.
UnregisterProvider unregisters a provider.
Watch returns a channel which you can range over.

# Constants

ConfigEnvVar defines the environment variable used to set up the configuration providers via InitFromEnvironment.
No description provided by the author

# Variables

No description provided by the author
Logs functions can be overriden.
No description provided by the author

# Structs

InMemoryProvider implements an in-memory configstore provider.
Item is a key/value pair with a priority attached.
ItemFilter holds a list of manipulation steps to operate on an item list.
ItemList is a list of items which can be manipulated by an ItemFilter.
No description provided by the author

# Type aliases

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
A Provider retrieves config items and makes them available to the configstore, Their implementations can vary wildly (HTTP API, file, env, hardcoded test, ...) and their results will get merged by the configstore library.
A ProviderFactory is a function that instantiates a Provider and registers it to a store instance.