2020-04-30 15:29:43 +00:00
|
|
|
package feature
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/opentracing/opentracing-go"
|
|
|
|
)
|
|
|
|
|
|
|
|
type contextKey string
|
|
|
|
|
|
|
|
const featureContextKey contextKey = "influx/feature/v1"
|
|
|
|
|
|
|
|
// Flagger returns flag values.
|
|
|
|
type Flagger interface {
|
|
|
|
// Flags returns a map of flag keys to flag values.
|
|
|
|
//
|
|
|
|
// If an authorization is present on the context, it may be used to compute flag
|
|
|
|
// values according to the affiliated user ID and its organization and other mappings.
|
|
|
|
// Otherwise, they should be computed generally or return a default.
|
|
|
|
//
|
|
|
|
// One or more flags may be provided to restrict the results.
|
|
|
|
// Otherwise, all flags should be computed.
|
|
|
|
Flags(context.Context, ...Flag) (map[string]interface{}, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Annotate the context with a map computed of computed flags.
|
|
|
|
func Annotate(ctx context.Context, f Flagger, flags ...Flag) (context.Context, error) {
|
|
|
|
computed, err := f.Flags(ctx, flags...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
span := opentracing.SpanFromContext(ctx)
|
|
|
|
if span != nil {
|
|
|
|
for k, v := range computed {
|
|
|
|
span.LogKV(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return context.WithValue(ctx, featureContextKey, computed), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FlagsFromContext returns the map of flags attached to the context
|
|
|
|
// by Annotate, or nil if none is found.
|
|
|
|
func FlagsFromContext(ctx context.Context) map[string]interface{} {
|
|
|
|
v, ok := ctx.Value(featureContextKey).(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2020-05-06 15:13:17 +00:00
|
|
|
type ByKeyFn func(string) (Flag, bool)
|
|
|
|
|
2020-04-30 15:29:43 +00:00
|
|
|
// ExposedFlagsFromContext returns the filtered map of exposed flags attached
|
|
|
|
// to the context by Annotate, or nil if none is found.
|
2020-05-06 15:13:17 +00:00
|
|
|
func ExposedFlagsFromContext(ctx context.Context, byKey ByKeyFn) map[string]interface{} {
|
2020-04-30 15:29:43 +00:00
|
|
|
m := FlagsFromContext(ctx)
|
|
|
|
if m == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
filtered := make(map[string]interface{})
|
|
|
|
for k, v := range m {
|
2020-05-06 15:13:17 +00:00
|
|
|
if flag, found := byKey(k); found && flag.Expose() {
|
2020-04-30 15:29:43 +00:00
|
|
|
filtered[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return filtered
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lifetime represents the intended lifetime of the feature flag.
|
|
|
|
//
|
|
|
|
// The zero value is Temporary, the most common case, but Permanent
|
|
|
|
// is included to mark special cases where a flag is not intended
|
|
|
|
// to be removed, e.g. enabling debug tracing for an organization.
|
|
|
|
//
|
|
|
|
// TODO(gavincabbage): This may become a stale date, which can then
|
|
|
|
// be used to trigger a notification to the contact when the flag
|
|
|
|
// has become stale, to encourage flag cleanup.
|
|
|
|
type Lifetime int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Temporary indicates a flag is intended to be removed after a feature is no longer in development.
|
|
|
|
Temporary Lifetime = iota
|
|
|
|
// Permanent indicates a flag is not intended to be removed.
|
|
|
|
Permanent
|
|
|
|
)
|
|
|
|
|
|
|
|
// UnmarshalYAML implements yaml.Unmarshaler and interprets a case-insensitive text
|
|
|
|
// representation as a lifetime constant.
|
|
|
|
func (l *Lifetime) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
|
|
var s string
|
|
|
|
if err := unmarshal(&s); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch strings.ToLower(s) {
|
|
|
|
case "permanent":
|
|
|
|
*l = Permanent
|
|
|
|
default:
|
|
|
|
*l = Temporary
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type defaultFlagger struct{}
|
|
|
|
|
|
|
|
// DefaultFlagger returns a flagger that always returns default values.
|
|
|
|
func DefaultFlagger() Flagger {
|
|
|
|
return &defaultFlagger{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flags returns a map of default values. It never returns an error.
|
|
|
|
func (*defaultFlagger) Flags(_ context.Context, flags ...Flag) (map[string]interface{}, error) {
|
|
|
|
if len(flags) == 0 {
|
|
|
|
flags = Flags()
|
|
|
|
}
|
|
|
|
|
|
|
|
m := make(map[string]interface{}, len(flags))
|
|
|
|
for _, flag := range flags {
|
|
|
|
m[flag.Key()] = flag.Default()
|
|
|
|
}
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flags returns all feature flags.
|
|
|
|
func Flags() []Flag {
|
|
|
|
return all
|
|
|
|
}
|
2020-05-06 15:13:17 +00:00
|
|
|
|
|
|
|
// ByKey returns the Flag corresponding to the given key.
|
|
|
|
func ByKey(k string) (Flag, bool) {
|
|
|
|
v, found := byKey[k]
|
|
|
|
return v, found
|
|
|
|
}
|