wave

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2017 License: MIT Imports: 2 Imported by: 0

README

Wave

Go programming language feature flagging

GoDoc Widget Build Status Coverage Status Code Climate Go Report Card MIT license

Inspired by the pyrollout python package by Jason Brechin. I've put no genius into this. I've just ported it and made it slightly more go idiomatic.

I've renamed it from rollout to wave simply because I am lazy and do not want to type rollout so many times in my go code. Wave is shorter and ebbs and flows like a software rollout too.

Installation & Tests

Using good ol' go get:

go get -u -v github.com/aisola/wave
go test github.com/aisola/wave/...

Using the new fangled dep:

dep ensure -update github.com/aisola/wave
go test github.com/aisola/wave/...

Typical Usage

While wave allows you to create and manage your own wave instances, the typical user of wave will simply underscore-import a backend and use the default instance. Here, since we are not underscore-importing ay different backend, wave uses in-memory storage.

// ...

import (
       "github.com/aisola/wave"
)

func main() {
     // ...
}

If you want undefined features grant access to all users, you can do that by marking the default Wave.UndefinedAccess field as true.

wave.Default.UndefinedAccess = true

Now add features:

// ...

// Open to all by using the special group 'ALL'
wave.AddFeature(wave.NewFeatureGroups("feature_for_all", wave.ALL))

// Open to select groups
wave.AddFeature(wave.NewFeatureGroups("feature_for_groups", []string{"vip", "early-adopter"}))

// Open to specific user(s), by user ID
wave.AddFeature(wave.NewFeatureUsers("feature_for_users", []string{"123", "456", "789"}))

// ...

Check access to features:

// ...

func UntestedFeature(user wave.User) bool {
     // Because this feature was not defined, access will always be denied, unless you've
     // set Wave.UndefinedAccess as true.
     if !wave.Can(user, "use_untested_feature") {
     	  return false
     }

     DoSomethingCool()

     return true
}


func FooHandler(w http.ResponseWriter, r *http.Request) {
     // The user type that we get from the request context is one passed in by
     // an authentication middleware of sorts. This user object should implement
     // the wave.User interface.
     ctx := r.Context()
     user := ctx.Value("user").(*User)

     if !wave.Can(user, "feature_for_users") {
     	  http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
	  return
     }

     DoSomethingElseCoolHere(w)
}

// ...

Creating multiple Wave instances

In some cases, you may want to create and use more than one instance of wave in your application. For instance, if you have two different APIs serving out of the same binary.

import (
       "github.com/aisola/wave"
       _ "github.com/some/wave/backend"
)
// ...

api1 := wave.NewWave("backend")
api2 := wave.NewWave("backend")

if err := api1.Open("backend://username:password@127.0.0.1:8000/api1"); err != nil {
   log.Fatalf("Could not open up wave backend for api1, %s", err)
}
defer api1.Close()

if err := api2.Open("backend://username:password@127.0.0.1:8000/api2"); err != nil {
   log.Fatalf("Could not open up wave backend for api2, %s", err)
}
defer api2.Close()

// Open to all by using the special group 'ALL'
api1.AddFeature(wave.NewFeatureGroups("feature_for_all", wave.ALL))

// Open to admins
api2.AddFeature(wave.NewFeatureGroups("feature_for_admins", []string{"admins"}))

// ...

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ALL is a shorthand for the ALL special group
	ALL = []string{"ALL"}

	// NONE is a shorthand for the NONE special group
	NONE = []string{"NONE"}
)
View Source
var Default = &Wave{}

Default is a Wave instance using the default InMemoryBackend.

View Source
var (
	// ErrFeatureNotFound should be returned by FeatureBackend.Get if the
	// feature cannot be found.
	ErrFeatureNotFound = errors.New("feature not found")
)

Functions

func AddFeature

func AddFeature(feature *Feature) error

AddFeature is a convenience function that calls Default.AddFeature.

func Can

func Can(user User, name string) bool

Can is a convenience function that calls Default.Can.

func Close

func Close() error

Close is a convenience function that calls Default.Close.

func Open

func Open(connection string) error

Open is a convenience function that calls Default.Open.

func Register

func Register(name string, backend FeatureBackend)

Register will register a FeatureBackend wave.

Types

type Feature

type Feature struct {
	Name   string
	Groups []string
	Users  []string
}

Feature is a specific feature and its constraints.

func NewFeatureGroups

func NewFeatureGroups(name string, groups []string) *Feature

NewFeatureGroups will create a new feature name, which only allows access to users belonging to the listed groups.

func NewFeatureUsers

func NewFeatureUsers(name string, users []string) *Feature

NewFeatureUsers will create a new feature name, which only allows access to the provided users.

func (*Feature) Can

func (f *Feature) Can(user User) bool

Can returns true if the user has access to this feature based on the contraints. This method checks if the user is allowed specificly by ID first, then by group.

type FeatureBackend

type FeatureBackend interface {
	// Open should take in a single argument of any type that it needs to
	// connect to its backend. Then it must connect to the backend or return
	// and error For instance, a SQLBackend might take in a MySQL connection
	// string and return connection timeout errors.
	Open(string) error

	// Close is called by the user to close the backend connection.
	Close() error

	// Get takes in a feature name string and returns the feature that
	// matches that name, or it should return ErrFeatureNotFound.
	Get(string) (*Feature, error)

	// Set is called by a Wave instance in order to add a feature to the
	// backend. The first argument is the feature name and the second is
	// the feature.
	Set(string, *Feature) error
}

FeatureBackend is the interface which different backends must implement in order to be used with wave.

type InMemoryBackend

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

InMemoryBackend is a FeatureBackend which simply holds the feature information in-memory. This is just a default so that it is there, this really shouldn't be used in real serious software.

func NewInMemoryBackend

func NewInMemoryBackend() *InMemoryBackend

NewInMemoryBackend creates a new InMemoryBackend.

func (*InMemoryBackend) Close

func (imb *InMemoryBackend) Close() error

Close wipes and clears the in-memory feature storage.

func (*InMemoryBackend) Get

func (imb *InMemoryBackend) Get(name string) (*Feature, error)

Get returns a feature by name if it exists.

func (*InMemoryBackend) Open

func (imb *InMemoryBackend) Open(string) error

Open literally does nothing. This is here to implement the FeatureBackend.

func (*InMemoryBackend) Set

func (imb *InMemoryBackend) Set(name string, feature *Feature) error

Set will create a feature given a feature name and a wave.Feature.

type User

type User interface {
	// ID returns the unique identitfier for the particular user.
	ID() string

	// Groups returns a string slice of group names to which the user belongs.
	Groups() []string
}

User must be implemented by any type wishing to have feature permissions managed by wave. For instance, your application's User must implement this.

type Wave

type Wave struct {

	// When UndefinedAccess is false, the users will NOT be granted access
	// to features of undefined names.
	UndefinedAccess bool
	// contains filtered or unexported fields
}

Wave manages all of the interactions between you and the FeatureBackend.

func New

func New(backend string) *Wave

New creates a new *Wave instance and sets the given FeatureBackend.

func (*Wave) AddFeature

func (w *Wave) AddFeature(feature *Feature) error

AddFeature adds a Feature to the wave instance.

func (*Wave) Can

func (w *Wave) Can(user User, name string) bool

Can returns true of the given user has access to the given feature.

func (*Wave) Close

func (w *Wave) Close() error

Close will safely close up (and persist if necessary) the FeatureBackend.

func (*Wave) Open

func (w *Wave) Open(connection string) error

Open connects to the FeatureBackend using the given connection string.

func (*Wave) SetBackend

func (w *Wave) SetBackend(backend string)

SetBackend sets the FeatureBackend for this wave instance. It will panic if the named backend is not registered.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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