Skip to content

Commit

Permalink
init feature gates
Browse files Browse the repository at this point in the history
  • Loading branch information
tonybase committed Jul 23, 2023
1 parent ca34965 commit 120b9f8
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 0 deletions.
54 changes: 54 additions & 0 deletions feautre.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package feature

import "sync/atomic"

// Stage is feature state.
type Stage int

const (
StageAlpha Stage = iota
StageBeta
StageStable
StageDeprecated
)

// Feature is the feature interface.
type Feature struct {
name string
enabled atomic.Bool
description string
fromVersion string
toVersion string
stage Stage
}

// Name returns feature name.
func (f *Feature) Name() string {
return f.name
}

// Enabled returns true if the feature enbaled.
func (f *Feature) Enabled() bool {
return f.enabled.Load()
}

// Description returns the description for the feature.
func (f *Feature) Description() string {
return f.description
}

// FromVersion The "From" column contains the Feature release when a feature is introduced or its release stage is changed.
func (f *Feature) FromVersion() string {
return f.fromVersion
}

// ToVersion if not empty, contains the last Feature release in which you can still use a feature gate.
// If the feature stage is either "Deprecated" or "GA", the "To" column is the Feature release when the feature is removed.
func (f *Feature) ToVersion() string {
return f.toVersion
}

// Stage returns the feature state.
func (f *Feature) Stage() Stage {
return f.stage
}
34 changes: 34 additions & 0 deletions gate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package feature

import "flag"

var globalRegistry = NewRegistry()

func init() {
flag.CommandLine.Var(globalRegistry, "feature-gates", "A set of key=value pairs that describe feature gates for alpha/beta/stable features.")
}

// Register register a feature gate.
func Register(name string, enabled bool, opts ...Option) (*Feature, error) {
return globalRegistry.Register(name, enabled, opts...)
}

// MustRegister must register a feature gate.
func MustRegister(name string, enabled bool, opts ...Option) *Feature {
return globalRegistry.MustRegister(name, enabled, opts...)
}

// Set parses the feature flags: foo=true,bar=false.
func Set(featureFlags string) error {
return globalRegistry.Set(featureFlags)
}

// SetEnabled set feature enabled.
func SetEnabled(name string, enabled bool) error {
return globalRegistry.SetEnabled(name, enabled)
}

// Visit visits all the features.
func Visit(f func(*Feature)) {
globalRegistry.Visit(f)
}
23 changes: 23 additions & 0 deletions gate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package feature_test

import (
"testing"

"github.com/go-kratos/feature"
)

var (
foo = feature.MustRegister("foo", true)
bar = feature.MustRegister("bar", false,
feature.WithFeautreStage(feature.StageAlpha),
feature.WithFeautreFromVersion("0.0.1"),
feature.WithFeautreToVersion("1.0.0"),
feature.WithFeautreDescription("A foo feature"),
)
)

func TestFeatureVisit(t *testing.T) {
feature.Visit(func(f *feature.Feature) {
t.Logf("feature: %s %t", f.Name(), f.Enabled())
})
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/go-kratos/feature

go 1.19
119 changes: 119 additions & 0 deletions registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package feature

import (
"fmt"
"strconv"
"strings"
)

// Option is feature option.
type Option func(*Feature)

// WithFeautreStage with the feature stage.
func WithFeautreStage(stage Stage) Option {
return func(f *Feature) {
f.stage = stage
}
}

// WithFeautreDescription with the feautre description.
func WithFeautreDescription(description string) Option {
return func(f *Feature) {
f.description = description
}
}

// WithFeautreToVersion with the feautre to version.
func WithFeautreToVersion(version string) Option {
return func(f *Feature) {
f.toVersion = version
}
}

// WithFeautreFromVersion with the feautre to version.
func WithFeautreFromVersion(version string) Option {
return func(f *Feature) {
f.toVersion = version
}
}

// Registry is feature gates registry.
type Registry struct {
features map[string]*Feature
}

// NewRegistry new a feature registry.
func NewRegistry() *Registry {
return &Registry{
features: make(map[string]*Feature),
}
}

// Register register a feature gate.
func (r *Registry) Register(name string, enabled bool, opts ...Option) (*Feature, error) {
if _, ok := r.features[name]; ok {
return nil, fmt.Errorf("feature gate %s is registered", name)
}
feature := &Feature{
name: name,
}
feature.enabled.Store(enabled)
for _, o := range opts {
o(feature)
}
r.features[feature.Name()] = feature
return feature, nil
}

// MustRegister must register a feature gate.
func (r *Registry) MustRegister(name string, enabled bool, opts ...Option) *Feature {
feature, err := r.Register(name, enabled, opts...)
if err != nil {
panic(err)
}
return feature
}

// SetEnabled set feature enabled.
func (r *Registry) SetEnabled(name string, enabled bool) error {
f, ok := r.features[name]
if !ok {
return fmt.Errorf("not found feature name: %s", name)
}
f.enabled.Store(enabled)
return nil
}

// Set parses the feature flags: foo=true,bar=false.
func (r *Registry) Set(featureFlags string) error {
fs := strings.Split(featureFlags, ",")
for _, s := range fs {
feature := strings.Split(s, "=")
name := feature[0]
enabled, err := strconv.ParseBool(feature[1])
if err != nil {
return err
}
if err := r.SetEnabled(name, enabled); err != nil {
return err
}
}
return nil
}

func (r *Registry) String() string {
pairs := []string{}
for name, feature := range r.features {
enabled := feature.enabled.Load()
pairs = append(pairs, fmt.Sprintf("%s=%t", name, enabled))
}
return strings.Join(pairs, ",")

}

// Visit visits all the features.
func (r *Registry) Visit(f func(*Feature)) {
for _, feature := range r.features {
f(feature)
}
}

0 comments on commit 120b9f8

Please sign in to comment.