config

package
v1.0.0-beta3.1 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2017 License: MIT Imports: 18 Imported by: 0

README

Configuration Package

At a high level, configuration is any data that is used in an application but not part of the application itself. Any reasonably complex system needs to have knobs to tune, and not everything can have intelligent defaults.

In UberFx, we try very hard to make configuring UberFx convenient. Users can:

  • Get components working with minimal configuration
  • Override any field if the default doesn't make sense for their use case

Nesting

The configuration system wraps a set of providers that each know how to get values from an underlying source:

  • Static YAML configuration
  • Environment variables

So by stacking these providers, we can have a priority system for defining configuration that can be overridden by higher priority providers. For example, the static YAML configuration would be the lowest priority and those values should be overridden by values specified as environment variables.

As an example, imagine a YAML config that looks like:

foo:
  bar:
    boo: 1
    baz: hello

stuff:
  server:
    port: 8081
    greeting: Hello There!

UberFx Config allows direct key access, such as foo.bar.baz:

cfg := svc.Config()
if value := cfg.Get("foo.bar.baz"); value.HasValue() {
  fmt.Println("Say", value.AsString()) // "Say hello"
}

Or via a strongly typed structure, even as a nest value, such as:

type myStuff struct {
  Port     int    `yaml:"port" default:"8080"`
  Greeting string `yaml:"greeting"`
}

// ....

target := &myStuff{}
cfg := svc.Config()
if err := cfg.Get("stuff.server").Populate(target); err != nil {
  // fail, we didn't find it.
}

fmt.Println("Port is", target.Port) // "Port is 8081"

This model respects priority of providers to allow overriding of individual values.

Provider

Provider is the interface for anything that can provide values. We provide a few reference implementations (environment and YAML), but you are free to register your own providers via config.RegisterProviders() and config.RegisterDynamicProviders.

Static configuration providers

Static configuration providers conform to the Provider interface and are bootstrapped first. Use these for simple providers such as file-backed or environment-based configuration providers.

Dynamic Configuration Providers

Dynamic configuration providers frequently need some bootstrap configuration to be useful, so UberFx treats them specially. Dynamic configuration providers conform to the Provider interface, but they're instantiated after the Static Providers on order to read bootstrap values.

For example, if you were to implement a ZooKeeper-backed Provider, you'd likely need to specify (via YAML or environment variables) where your ZooKeeper nodes live.

Value

Value is the return type of every configuration providers' Get(key string) method. Under the hood, we use the empty interface (interface{}) since we don't necessarily know the structure of your configuration ahead of time.

You can use a Value for two main purposes:

  • Get a single value out of configuration.

For example, if we have a YAML configuration like so:

one:
  two: hello

You could access the value using "dotted notation":

foo := provider.Get("one.two").AsString()
fmt.Println(foo)
// Output: hello

To get an access to the root element use config.Root:

root := provider.Get(config.Root).AsString()
fmt.Println(root)
// Output: map[one:map[two:hello]]
  • Populate a struct (Populate(&myStruct))

The As* method has two variants: TryAs* and As*. The former is a two-value return, similar to a type assertion, where the user checks if the second bool is true before using the value.

The As* methods are similar to the Must* pattern in the standard library. If the underlying value cannot be converted to the requested type, As* will panic.

Populate

Populate is akin to json.Unmarshal() in that it takes a pointer to a custom struct and fills in the fields. It returns a true if the requested fields were found and populated properly, and false otherwise.

For example, say we have the following YAML file:

hello:
  world: yes
  number: 42

We could deserialize into our custom type with the following code:

type myConfig struct {
  World  string
  Number int
}

m := myConfig{}
provider.Get("hello").Populate(&m)
fmt.Println(m.World)
// Output: yes

Note that any fields you wish to deserialize into must be exported, just like json.Unmarshal and friends.

Benchmarks

Current performance benchmark data:

BenchmarkYAMLCreateSingleFile-8                    119 allocs/op
BenchmarkYAMLCreateMultiFile-8                     203 allocs/op
BenchmarkYAMLSimpleGetLevel1-8                       0 allocs/op
BenchmarkYAMLSimpleGetLevel3-8                       0 allocs/op
BenchmarkYAMLSimpleGetLevel7-8                       0 allocs/op
BenchmarkYAMLPopulate-8                             16 allocs/op
BenchmarkYAMLPopulateNested-8                       42 allocs/op
BenchmarkYAMLPopulateNestedMultipleFiles-8          52 allocs/op
BenchmarkYAMLPopulateNestedTextUnmarshaler-8       211 allocs/op
BenchmarkZapConfigLoad-8                           188 allocs/op

Environment Variables

YAML provider supports accepting values from the environment. For example, consider the following YAML file:

modules:
  http:
    port: ${HTTP_PORT:3001}

Upon loading file, YAML provider will look up the HTTP_PORT environment variable and if available use it's value. If it's not found, the provided 3001 default will be used.

Documentation

Overview

Package config is the Configuration Package.

At a high level, configuration is any data that is used in an application but not part of the application itself. Any reasonably complex system needs to have knobs to tune, and not everything can have intelligent defaults.

In UberFx, we try very hard to make configuring UberFx convenient. Users can:

• Get components working with minimal configuration

• Override any field if the default doesn't make sense for their use case

Nesting

The configuration system wraps a set of providers that each know how to get values from an underlying source:

• Static YAML configuration

• Environment variables

So by stacking these providers, we can have a priority system for defining configuration that can be overridden by higher priority providers. For example, the static YAML configuration would be the lowest priority and those values should be overridden by values specified as environment variables.

As an example, imagine a YAML config that looks like:

foo:
  bar:
    boo: 1
    baz: hello

stuff:
  server:
    port: 8081
    greeting: Hello There!

UberFx Config allows direct key access, such as foo.bar.baz:

cfg := svc.Config()
if value := cfg.Get("foo.bar.baz"); value.HasValue() {
  fmt.Println("Say", value.AsString()) // "Say hello"
}

Or via a strongly typed structure, even as a nest value, such as:

type myStuff struct {
  Port     int    `yaml:"port" default:"8080"`
  Greeting string `yaml:"greeting"`
}

// ....

target := &myStuff{}
cfg := svc.Config()
if err := cfg.Get("stuff.server").Populate(target); err != nil {
  // fail, we didn't find it.
}

fmt.Println("Port is", target.Port) // "Port is 8081"

This model respects priority of providers to allow overriding of individual values.

Provider

Provider is the interface for anything that can provide values. We provide a few reference implementations (environment and YAML), but you are free to register your own providers via config.RegisterProviders() and config.RegisterDynamicProviders.

Static configuration providers

Static configuration providers conform to the Provider interface and are bootstrapped first. Use these for simple providers such as file-backed or environment-based configuration providers.

Dynamic Configuration Providers

Dynamic configuration providers frequently need some bootstrap configuration to be useful, so UberFx treats them specially. Dynamic configuration providers conform to the Provider interface, but they're instantiated after the Static Providers on order to read bootstrap values.

For example, if you were to implement a ZooKeeper-backed Provider, you'd likely need to specify (via YAML or environment variables) where your ZooKeeper nodes live.

Value

Value is the return type of every configuration providers' Get(key string) method. Under the hood, we use the empty interface ( interface{}) since we don't necessarily know the structure of your configuration ahead of time.

You can use a Value for two main purposes:

• Get a single value out of configuration.

For example, if we have a YAML configuration like so:

one:
  two: hello

You could access the value using "dotted notation":

foo := provider.Get("one.two").AsString()
fmt.Println(foo)
// Output: hello

To get an access to the root element use config.Root:

root := provider.Get(config.Root).AsString()
fmt.Println(root)
// Output: map[one:map[two:hello]]

• Populate a struct (Populate(&myStruct))

The As* method has two variants: TryAs* and As*. The former is a two-value return, similar to a type assertion, where the user checks if the second bool is true before using the value.

The As* methods are similar to the Must* pattern in the standard library. If the underlying value cannot be converted to the requested type, As* will panic.

Populate

Populate is akin to json.Unmarshal() in that it takes a pointer to a custom struct and fills in the fields. It returns a true if the requested fields were found and populated properly, and false otherwise.

For example, say we have the following YAML file:

hello:
  world: yes
  number: 42

We could deserialize into our custom type with the following code:

type myConfig struct {
  World  string
  Number int
}

m := myConfig{}
provider.Get("hello").Populate(&m)
fmt.Println(m.World)
// Output: yes

Note that any fields you wish to deserialize into must be exported, just like json.Unmarshal and friends.

Benchmarks

Current performance benchmark data:

BenchmarkYAMLCreateSingleFile-8                    119 allocs/op
BenchmarkYAMLCreateMultiFile-8                     203 allocs/op
BenchmarkYAMLSimpleGetLevel1-8                       0 allocs/op
BenchmarkYAMLSimpleGetLevel3-8                       0 allocs/op
BenchmarkYAMLSimpleGetLevel7-8                       0 allocs/op
BenchmarkYAMLPopulate-8                             16 allocs/op
BenchmarkYAMLPopulateNested-8                       42 allocs/op
BenchmarkYAMLPopulateNestedMultipleFiles-8          52 allocs/op
BenchmarkYAMLPopulateNestedTextUnmarshaler-8       211 allocs/op
BenchmarkZapConfigLoad-8                           188 allocs/op

Environment Variables

YAML provider supports accepting values from the environment. For example, consider the following YAML file:

modules:
  http:
    port: ${HTTP_PORT:3001}

Upon loading file, YAML provider will look up the HTTP_PORT environment variable and if available use it's value. If it's not found, the provided 3001 default will be used.

Index

Constants

View Source
const (
	// ServiceNameKey is the config key of the service name
	ServiceNameKey = "name"
	// ServiceDescriptionKey is the config key of the service
	// description
	ServiceDescriptionKey = "description"
	// ServiceOwnerKey is the config key for a service owner
	ServiceOwnerKey = "owner"
)
View Source
const Root = ""

Root marks the root node in a Provider

Variables

This section is empty.

Functions

func AppRoot

func AppRoot() string

AppRoot returns the root directory of your application. UberFx developers can edit this via the APP_ROOT environment variable. If the environment variable is not set then it will fallback to the current working directory.

func Environment

func Environment() string

Environment returns current environment setup for the service

func EnvironmentKey

func EnvironmentKey() string

EnvironmentKey returns environment variable key name

func EnvironmentPrefix

func EnvironmentPrefix() string

EnvironmentPrefix returns environment prefix for the application

func Path

func Path() string

Path returns path to the yaml configurations

func RegisterDynamicProviders

func RegisterDynamicProviders(dynamicProviderFuncs ...DynamicProviderFunc)

RegisterDynamicProviders registers dynamic config providers for the global config Dynamic provider initialization needs access to Provider for accessing necessary information for bootstrap, such as port number,keys, endpoints etc.

func RegisterProviders

func RegisterProviders(providerFuncs ...ProviderFunc)

RegisterProviders registers configuration providers for the global config

func ResolvePath

func ResolvePath(relative string) (string, error)

ResolvePath returns an absolute path derived from AppRoot and the relative path. If the input parameter is already an absolute path it will be returned immediately.

func SetConfigFiles

func SetConfigFiles(files ...string)

SetConfigFiles overrides the set of available config files for the service

func SetEnvironmentPrefix

func SetEnvironmentPrefix(envPrefix string)

SetEnvironmentPrefix sets environment prefix for the application

func UnregisterProviders

func UnregisterProviders()

UnregisterProviders clears all the default providers

Types

type ChangeCallback

type ChangeCallback func(key string, provider string, data interface{})

ChangeCallback is called for updates of configuration data

type DynamicProviderFunc

type DynamicProviderFunc func(config Provider) (Provider, error)

DynamicProviderFunc is used to create config providers on configuration initialization

type FileResolver

type FileResolver interface {
	Resolve(file string) io.ReadCloser
}

A FileResolver resolves references to files

func NewRelativeResolver

func NewRelativeResolver(paths ...string) FileResolver

NewRelativeResolver returns a file resolver relative to the given paths

type MockDynamicProvider

type MockDynamicProvider struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

MockDynamicProvider is simple implementation of Provider that can be used to test dynamic features. It is safe to use with multiple go routines, but doesn't support nested objects.

func NewMockDynamicProvider

func NewMockDynamicProvider(data map[string]interface{}) *MockDynamicProvider

NewMockDynamicProvider returns a new MockDynamicProvider

func (*MockDynamicProvider) Get

func (s *MockDynamicProvider) Get(key string) Value

Get returns a value in the map.

func (*MockDynamicProvider) Name

func (s *MockDynamicProvider) Name() string

Name is MockDynamicProvider

func (*MockDynamicProvider) RegisterChangeCallback

func (s *MockDynamicProvider) RegisterChangeCallback(key string, callback ChangeCallback) error

RegisterChangeCallback registers a callback to be called when a value associated with a key will change.

func (*MockDynamicProvider) Set

func (s *MockDynamicProvider) Set(key string, value interface{})

Set value to specific key and then calls a corresponding callback.

func (*MockDynamicProvider) UnregisterChangeCallback

func (s *MockDynamicProvider) UnregisterChangeCallback(token string) error

UnregisterChangeCallback removes a callback associated with a token.

type NopProvider

type NopProvider struct{}

NopProvider is an implementation of config provider that does nothing. It should be used for testing purposes only.

func (NopProvider) Get

func (p NopProvider) Get(key string) Value

Get returns an invalid Value.

func (NopProvider) Name

func (p NopProvider) Name() string

Name is NopProvider

func (NopProvider) RegisterChangeCallback

func (p NopProvider) RegisterChangeCallback(key string, callback ChangeCallback) error

RegisterChangeCallback does nothing and simply returns nil.

func (NopProvider) UnregisterChangeCallback

func (p NopProvider) UnregisterChangeCallback(token string) error

UnregisterChangeCallback does nothing and simply returns nil.

type Provider

type Provider interface {
	// the Name of the provider (YAML, Env, etc)
	Name() string
	// Get pulls a config value
	Get(key string) Value

	// A RegisterChangeCallback provides callback registration for config providers.
	// These callbacks are nop if a dynamic provider is not configured for the service.
	RegisterChangeCallback(key string, callback ChangeCallback) error
	UnregisterChangeCallback(token string) error
}

A Provider provides a unified interface to accessing configuration systems.

func Load

func Load() Provider

Load creates a Provider for use in a service

func NewCachedProvider

func NewCachedProvider(p Provider) Provider

NewCachedProvider returns a provider, that caches values of the underlying Provider p. It also subscribes for changes for all keys that ever retrieved from the provider. If the underlying provider fails to register callback for a particular value, it will return the underlying error wrapped in Value.

func NewMultiCallbackProvider

func NewMultiCallbackProvider(p Provider) Provider

NewMultiCallbackProvider returns a provider that lets you to have multiple callbacks for a given Provider. All registered callbacks are going to be executed in the order they were registered. UnregisterCallback will unregister the most recently registered callback.

func NewProviderGroup

func NewProviderGroup(name string, providers ...Provider) Provider

NewProviderGroup creates a configuration provider from a group of backends

func NewScopedProvider

func NewScopedProvider(prefix string, provider Provider) Provider

NewScopedProvider creates a child provider given a prefix

func NewStaticProvider

func NewStaticProvider(data interface{}) Provider

NewStaticProvider should only be used in tests to isolate config from your environment It is not race free, because underlying objects can be accessed with Value().

func NewYAMLProviderFromBytes

func NewYAMLProviderFromBytes(yamls ...[]byte) Provider

NewYAMLProviderFromBytes creates a config provider from a byte-backed YAML blobs. As above, all the objects are going to be merged and arrays/values overridden in the order of the yamls.

func NewYAMLProviderFromFiles

func NewYAMLProviderFromFiles(mustExist bool, resolver FileResolver, files ...string) Provider

NewYAMLProviderFromFiles creates a configuration provider from a set of YAML file names. All the objects are going to be merged and arrays/values overridden in the order of the files.

func NewYAMLProviderFromReader

func NewYAMLProviderFromReader(readers ...io.ReadCloser) Provider

NewYAMLProviderFromReader creates a configuration provider from a list of `io.ReadClosers`. As above, all the objects are going to be merged and arrays/values overridden in the order of the files.

type ProviderFunc

type ProviderFunc func() (Provider, error)

ProviderFunc is used to create config providers on configuration initialization

func Providers

func Providers() []ProviderFunc

Providers should only be used during tests

func StaticProvider

func StaticProvider(data interface{}) ProviderFunc

StaticProvider returns function to create StaticProvider during configuration initialization

func YamlProvider

func YamlProvider() ProviderFunc

YamlProvider returns function to create Yaml based configuration provider

type RelativeResolver

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

A RelativeResolver resolves files relative to the given paths

func (RelativeResolver) Resolve

func (rr RelativeResolver) Resolve(file string) io.ReadCloser

Resolve finds a reader relative to the given resolver

type Value

type Value struct {
	Timestamp time.Time
	Type      ValueType
	// contains filtered or unexported fields
}

A Value holds the value of a configuration

func NewValue

func NewValue(
	provider Provider,
	key string,
	value interface{},
	found bool,
	t ValueType,
	timestamp *time.Time,
) Value

NewValue creates a configuration value from a provider and a set of parameters describing the key

func (Value) AsBool

func (cv Value) AsBool() bool

AsBool returns the configuration value as an bool, or panics if not bool-able

func (Value) AsFloat

func (cv Value) AsFloat() float64

AsFloat returns the configuration value as an float64, or panics if not float64-able

func (Value) AsInt

func (cv Value) AsInt() int

AsInt returns the configuration value as an int, or panics if not int-able

func (Value) AsString

func (cv Value) AsString() string

AsString returns the configuration value as a string, or panics if not string-able

func (Value) ChildKeys

func (cv Value) ChildKeys() []string

ChildKeys returns the child keys TODO(ai) what is this and do we need to keep it?

func (Value) Get

func (cv Value) Get(key string) Value

Get returns a value scoped in the current value

func (Value) HasValue

func (cv Value) HasValue() bool

HasValue returns whether the configuration has a value that can be used

func (Value) IsDefault

func (cv Value) IsDefault() bool

IsDefault returns whether the return value is the default.

func (Value) LastUpdated

func (cv Value) LastUpdated() time.Time

LastUpdated returns when the configuration value was last updated

func (Value) Populate

func (cv Value) Populate(target interface{}) error

Populate fills in an object from configuration

func (Value) Source

func (cv Value) Source() string

Source returns a configuration provider's name

func (Value) String

func (cv Value) String() string

String prints out underline value in Value with fmt.Sprint.

func (Value) TryAsBool

func (cv Value) TryAsBool() (bool, bool)

TryAsBool attempts to return the configuration value as a bool

func (Value) TryAsFloat

func (cv Value) TryAsFloat() (float64, bool)

TryAsFloat attempts to return the configuration value as a float

func (Value) TryAsInt

func (cv Value) TryAsInt() (int, bool)

TryAsInt attempts to return the configuration value as an int

func (Value) TryAsString

func (cv Value) TryAsString() (string, bool)

TryAsString attempts to return the configuration value as a string

func (Value) Value

func (cv Value) Value() interface{}

Value returns the underlying configuration's value

func (Value) WithDefault

func (cv Value) WithDefault(value interface{}) Value

WithDefault creates a shallow copy of the current configuration value and sets its default.

type ValueType

type ValueType int

A ValueType is a type-description of a configuration value

const (
	// Invalid represents an unset or invalid config type
	Invalid ValueType = iota
	// String is, well, you know what it is
	String
	// Integer holds numbers without decimals
	Integer
	// Bool is, well... go check Wikipedia. It's complicated.
	Bool
	// Float is an easy one. They don't sink in pools.
	Float
	// Slice will cut you.
	Slice
	// Dictionary contains words and their definitions
	Dictionary
)

func GetType

func GetType(value interface{}) ValueType

GetType returns GO type of the provided object

Jump to

Keyboard shortcuts

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