influxdb/kit/feature/feature.go

141 lines
3.6 KiB
Go
Raw Normal View History

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
}
type ByKeyFn func(string) (Flag, bool)
// ExposedFlagsFromContext returns the filtered map of exposed flags attached
// to the context by Annotate, or nil if none is found.
func ExposedFlagsFromContext(ctx context.Context, byKey ByKeyFn) map[string]interface{} {
m := FlagsFromContext(ctx)
if m == nil {
return nil
}
filtered := make(map[string]interface{})
for k, v := range m {
if flag, found := byKey(k); found && flag.Expose() {
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
}
// ByKey returns the Flag corresponding to the given key.
func ByKey(k string) (Flag, bool) {
v, found := byKey[k]
return v, found
}