distconf

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2019 License: Apache-2.0 Imports: 11 Imported by: 0

README

distconf

CircleCI GoDoc codecov

distconf is a distributed configuration framework for Go.

All applications need to load configuration somehow. Configuration can be loaded in many different ways

  • Environment variables
  • Command line parameters
  • ZooKeeper or consul

How configuration is loaded should ideally be abstracted from the need for configuration.

An additional complication is that configuration can change while an application is live. It is sometimes useful to allow applications to update their configuration without having to restart. Unfortunately, systems like zookeeper can be slow so your application also needs to atomically cache configuration, while also monitoring for changes.

Distconf does all that

  • Abstract the need for configuration from the source of configuration
  • Fast, atomic loading of configuration
  • Monitoring of configuration updates

Usage

The correct way to use distconf is to get a configuration value once from it, then either pass that value into your application code or register a watch to update your application when the value changes. For example, for type Float call distconf.Float once, then call Get on that value while your application is live.

Normal example with for loop

    func ExampleFloat_Get_inloop() {
        ctx := context.Background()
        m := distconf.Mem{}
        if err := m.Write(ctx, "value", []byte("2.0")); err != nil {
            panic("never happens")
        }
        d := distconf.Distconf{
            Readers: []distconf.Reader{&m},
        }
        x := d.Float(ctx, "value", 1.0)
        sum := 0.0
        for i := 0 ;i < 1000; i++ {
            sum += x.Get()
        }
        fmt.Println(sum)
        // Output: 2000
    }

Getting a float value from distconf

    func ExampleDistconf_Float() {
        ctx := context.Background()
        m := distconf.Mem{}
        if err := m.Write(ctx, "value", []byte("3.2")); err != nil {
            panic("never happens")
        }
        d := distconf.Distconf{
            Readers: []distconf.Reader{&m},
        }
        x := d.Float(ctx, "value", 1.0)
        fmt.Println(x.Get())
        // Output: 3.2
    }

Getting the default value from distconf

    func ExampleDistconf_defaults() {
        ctx := context.Background()
        d := distconf.Distconf{}
        x := d.Float(ctx, "value", 1.1)
        fmt.Println(x.Get())
        // Output: 1.1
    }

Watching for updates for values

    func ExampleFloat_Watch() {
        ctx := context.Background()
        m := distconf.Mem{}
        d := distconf.Distconf{
            Readers: []distconf.Reader{&m},
        }
        x := d.Float(ctx, "value", 1.0)
        x.Watch(func(f *distconf.Float, oldValue float64) {
            fmt.Println("Change from", oldValue, "to", f.Get())
        })
        fmt.Println("first", x.Get())
        if err := m.Write(ctx, "value", []byte("2.1")); err != nil {
            panic("never happens")
        }
        fmt.Println("second", x.Get())
        // Output: first 1
        // Change from 1 to 2.1
        // second 2.1
    }

Design Rational

The primary design goals of distconf are:

  • Obey best practices around lack of globals or reflection
  • Have small core interfaces with very few functions
  • Allow fast, atomic fetches of values, allowing them to be used in tight loops
  • Allow registered updates of values as they change
  • Minimal external dependencies

The core component of distconf is an interface with only one method.

    // Reader can get a []byte value for a config key
    type Reader interface {
        // Read should lookup a key inside the configuration source.  This function should
        // be thread safe, but is allowed to be slow or block.  That block will only happen
        // on application startup.  An error will skip this source and fall back to another
        // source in the chain.
        Read(ctx context.Context, key string) ([]byte, error)
    }

From this simple interface, we can derive the rest of distconf. Readers are used by Distconf to fetch configuration information. Some readers, such as zookeeper, allow you to watch for specific keys and get notifications live when they change. To support this, readers can also optionally implement the Watcher interface.

    // A Watcher config can change what it thinks a value is over time.
    type Watcher interface {
        // Watch a key for a change in value.  When the value for that key changes,
        // execute 'callback'.  It is ok to execute callback more times than needed.
        // Each call to callback will probably trigger future calls to Get().
        // Watch may be called multiple times for a single key.  Only the latest callback needs to be executed.
        // It is possible callback may itself call watch.  Be careful with locking.
        // If callback is nil, then we are trying to remove a previously registered callback.
        Watch(ctx context.Context, key string, callback func()) error
    }

Any Reader that implements Watcher will get a Watch() function call whenever a key should be watched for changes. That Watcher should then forever notify Distconf whenever that key's value changes by executing callback. When you're done with distconf, call Shutdown to deregister watches. It will try to exit early if context is terminated.

Contributing

Contributions welcome! Submit a pull request on github and make sure your code passes make lint test. For large changes, I strongly recommend creating an issue on GitHub first to confirm your change will be accepted before writing a lot of code. GitHub issues are also recommended, at your discretion, for smaller changes or questions.

License

This library is licensed under the Apache 2.0 License, forked from https://github.com/signalfx/golib under the Apache 2.0 License.

Documentation

Overview

Package distconf is a distributed configuration framework for go.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bool

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

Bool is a Boolean type config inside a Config. It uses strconv.ParseBool to parse the conf contents as either true for false

func (*Bool) Get

func (s *Bool) Get() bool

Get the boolean in this config variable

func (*Bool) Watch

func (s *Bool) Watch(watch BoolWatch)

Watch adds a watch for changes to this structure

type BoolWatch

type BoolWatch func(str *Bool, oldValue bool)

BoolWatch is executed if registered on a Bool variable any time the Bool contents change

type CommandLine

type CommandLine struct {
	Prefix string
	Source []string
}

func (*CommandLine) Read

func (p *CommandLine) Read(_ context.Context, key string) ([]byte, error)

type Distconf

type Distconf struct {
	// Hooks are optional callbacks that let you get information about the internal workings and errors of distconf.
	Hooks Hooks
	// Readers are an ordered list of backends for DistConf to get configuration information from.  The list
	// order is important as information from a first backend will be returned before the later ones.
	Readers []Reader
	// How long to timeout out of band refresh calls triggered by Watch() callbacks.  Defaults to 1 second.
	RefreshTimeout time.Duration
	// contains filtered or unexported fields
}

Distconf gets configuration data from the first backing that has it. It is a race condition to modify Hooks or Readers after you've started using Distconf. All public functions of Distconf are thread safe.

Example
package main

import (
	"context"
	"fmt"

	"github.com/cep21/distconf"
)

func main() {
	ctx := context.Background()
	m := distconf.Mem{}
	if err := m.Write(ctx, "value", []byte("true")); err != nil {
		panic("never happens")
	}
	d := distconf.Distconf{
		Readers: []distconf.Reader{&m},
	}
	x := d.Bool(ctx, "value", false)
	fmt.Println(x.Get())
}
Output:

true
Example (Defaults)
package main

import (
	"context"
	"fmt"

	"github.com/cep21/distconf"
)

func main() {
	ctx := context.Background()
	d := distconf.Distconf{}
	x := d.Float(ctx, "value", 1.1)
	fmt.Println(x.Get())
}
Output:

1.1

func (*Distconf) Bool

func (c *Distconf) Bool(ctx context.Context, key string, defaultVal bool) *Bool

Bool object that can be referenced to get boolean values from a backing config

Example
package main

import (
	"context"
	"fmt"

	"github.com/cep21/distconf"
)

func main() {
	ctx := context.Background()
	m := distconf.Mem{}
	if err := m.Write(ctx, "value", []byte("true")); err != nil {
		panic("never happens")
	}
	d := distconf.Distconf{
		Readers: []distconf.Reader{&m},
	}
	x := d.Bool(ctx, "value", false)
	fmt.Println(x.Get())
}
Output:

true

func (*Distconf) Duration

func (c *Distconf) Duration(ctx context.Context, key string, defaultVal time.Duration) *Duration

Duration returns a duration object that calls ParseDuration() on the given key

func (*Distconf) Float

func (c *Distconf) Float(ctx context.Context, key string, defaultVal float64) *Float

Float object that can be referenced to get float values from a backing config

Example
package main

import (
	"context"
	"fmt"

	"github.com/cep21/distconf"
)

func main() {
	ctx := context.Background()
	m := distconf.Mem{}
	if err := m.Write(ctx, "value", []byte("3.2")); err != nil {
		panic("never happens")
	}
	d := distconf.Distconf{
		Readers: []distconf.Reader{&m},
	}
	x := d.Float(ctx, "value", 1.0)
	fmt.Println(x.Get())
}
Output:

3.2

func (*Distconf) Info

func (c *Distconf) Info() expvar.Var

Info returns an expvar variable that shows the information for all configuration variables. Information consist of file, line, default value and type of variable.

func (*Distconf) Int

func (c *Distconf) Int(ctx context.Context, key string, defaultVal int64) *Int

Int object that can be referenced to get integer values from a backing config.

func (*Distconf) Refresh

func (c *Distconf) Refresh(ctx context.Context, key string)

Refresh a single key from readers. This will force a blocking Read from the Readers, in order, until one of them has the key. It will then update the distconf value for that key and trigger any update callbacks. You do not generally need to call this. If your backends implement Watcher, they will trigger this for you.

func (*Distconf) Shutdown

func (c *Distconf) Shutdown(ctx context.Context) error

Shutdown this config framework's Readers. Config variable results are undefined after this call. Returns the error of the first reader to return an error. While Watch itself doesn't take a context, shutdown will stop calling future watches if context ends.

func (*Distconf) Str

func (c *Distconf) Str(ctx context.Context, key string, defaultVal string) *Str

Str object that can be referenced to get string values from a backing config

func (*Distconf) Var

func (c *Distconf) Var() expvar.Var

Var returns an expvar variable that shows all the current configuration variables and their current value

Example
package main

import (
	"expvar"

	"github.com/cep21/distconf"
)

func main() {
	d := distconf.Distconf{}
	expvar.Publish("distconf", d.Var())
}
Output:

type Duration

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

Duration is a duration type config inside a Config.

func (*Duration) Get

func (s *Duration) Get() time.Duration

Get the string in this config variable

func (*Duration) Watch

func (s *Duration) Watch(watch DurationWatch)

Watch adds a watch for changes to this structure

type DurationWatch

type DurationWatch func(duration *Duration, oldValue time.Duration)

DurationWatch is executed if registered on a Duration variable any time the contents change

type Environment

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

func (*Environment) Read

func (p *Environment) Read(_ context.Context, key string) ([]byte, error)

type Float

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

Float is an float type config inside a Config.

func (*Float) Get

func (c *Float) Get() float64

Get the float in this config variable

Example (Inloop)
package main

import (
	"context"
	"fmt"

	"github.com/cep21/distconf"
)

func main() {
	ctx := context.Background()
	m := distconf.Mem{}
	if err := m.Write(ctx, "value", []byte("2.0")); err != nil {
		panic("never happens")
	}
	d := distconf.Distconf{
		Readers: []distconf.Reader{&m},
	}
	x := d.Float(ctx, "value", 1.0)
	sum := 0.0
	for i := 0; i < 1000; i++ {
		sum += x.Get()
	}
	fmt.Println(sum)
}
Output:

2000

func (*Float) Watch

func (c *Float) Watch(watch FloatWatch)

Watch for changes to this variable.

Example
package main

import (
	"context"
	"fmt"

	"github.com/cep21/distconf"
)

func main() {
	ctx := context.Background()
	m := distconf.Mem{}
	d := distconf.Distconf{
		Readers: []distconf.Reader{&m},
	}
	x := d.Float(ctx, "value", 1.0)
	x.Watch(func(f *distconf.Float, oldValue float64) {
		fmt.Println("Change from", oldValue, "to", f.Get())
	})
	fmt.Println("first", x.Get())
	if err := m.Write(ctx, "value", []byte("2.1")); err != nil {
		panic("never happens")
	}
	fmt.Println("second", x.Get())
}
Output:

first 1
Change from 1 to 2.1
second 2.1

type FloatWatch

type FloatWatch func(float *Float, oldValue float64)

FloatWatch is called on any changes to a register integer config variable

type Hooks

type Hooks struct {
	// OnError is called whenever there is an error doing something internally to distconf, but the error itself
	// cannot be directly returned to the caller.
	// distconfKey is the key that caused the error.
	OnError func(msg string, distconfKey string, err error)
}

Hooks are optional callbacks that let you get information about the internal workings and errors of distconf. All functions of Hooks should be thread safe.

type Int

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

Int is an integer type config inside a Config.

func (*Int) Get

func (c *Int) Get() int64

Get the integer in this config variable

func (*Int) Watch

func (c *Int) Watch(watch IntWatch)

Watch for changes to this variable.

type IntWatch

type IntWatch func(str *Int, oldValue int64)

IntWatch is called on any changes to a register integer config variable

type Mem

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

func (*Mem) Read

func (m *Mem) Read(_ context.Context, key string) ([]byte, error)

func (*Mem) Watch

func (m *Mem) Watch(_ context.Context, key string, callback func()) error

func (*Mem) Write

func (m *Mem) Write(_ context.Context, key string, value []byte) error

type Reader

type Reader interface {
	// Read should lookup a key inside the configuration source.  This function should
	// be thread safe, but is allowed to be slow or block.  That block will only happen
	// on application startup.  An error will skip this source and fall back to another
	// source in the chain.  If the value is not inside this reader, return nil, nil.
	Read(ctx context.Context, key string) ([]byte, error)
}

Reader can get a []byte value for a config key

type Shutdownable

type Shutdownable interface {
	// Shutdown should signal to a reader it is no longer needed by Distconf. It should expect
	// to no longer require to return more recent values to distconf.
	Shutdown(ctx context.Context) error
}

Shutdownable is an optional interface of Reader that allows it to be gracefully shutdown.

type Str

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

Str is a string type config inside a Config.

func (*Str) Get

func (s *Str) Get() string

Get the string in this config variable

func (*Str) Watch

func (s *Str) Watch(watch StrWatch)

Watch adds a watch for changes to this structure

type StrWatch

type StrWatch func(str *Str, oldValue string)

StrWatch is executed if registered on a Str variable any time the Str contents change

type Watcher

type Watcher interface {
	// Watch a key for a change in value.  When the value for that key changes,
	// execute 'callback'.  It is ok to execute callback more times than needed.
	// Each call to callback will probably trigger future calls to Get().
	// Watch may be called multiple times for a single key.  Only the latest callback needs to be executed.
	// It is possible callback may itself call watch.  Be careful with locking.
	// If callback is nil, then we are trying to remove a previously registered callback.
	Watch(ctx context.Context, key string, callback func()) error
}

A Watcher config can change what it thinks a value is over time.

Jump to

Keyboard shortcuts

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