diff --git a/cmd/influxd/launcher/launcher.go b/cmd/influxd/launcher/launcher.go index f9cfe1d24b..21082cd5fa 100644 --- a/cmd/influxd/launcher/launcher.go +++ b/cmd/influxd/launcher/launcher.go @@ -906,6 +906,7 @@ func (m *Launcher) run(ctx context.Context) (err error) { WriteEventRecorder: infprom.NewEventRecorder("write"), QueryEventRecorder: infprom.NewEventRecorder("query"), Flagger: flagger, + FlagsHandler: feature.NewFlagsHandler(kithttp.ErrorHandler(0), feature.ByKey), } m.reg.MustRegister(m.apibackend.PrometheusCollectors()...) diff --git a/http/api_handler.go b/http/api_handler.go index 3085338bf9..c6b39580bb 100644 --- a/http/api_handler.go +++ b/http/api_handler.go @@ -84,6 +84,7 @@ type APIBackend struct { NotificationRuleStore influxdb.NotificationRuleStore NotificationEndpointService influxdb.NotificationEndpointService Flagger feature.Flagger + FlagsHandler http.Handler } // PrometheusCollectors exposes the prometheus collectors associated with an APIBackend. @@ -205,7 +206,7 @@ func NewAPIHandler(b *APIBackend, opts ...APIHandlerOptFn) *APIHandler { userHandler := NewUserHandler(b.Logger, userBackend) h.Mount(prefixMe, userHandler) h.Mount(prefixUsers, userHandler) - h.Mount("/api/v2/flags", serveFlagsHandler(b.HTTPErrorHandler)) + h.Mount("/api/v2/flags", b.FlagsHandler) variableBackend := NewVariableBackend(b.Logger.With(zap.String("handler", "variable")), b) variableBackend.VariableService = authorizer.NewVariableService(b.VariableService) @@ -281,16 +282,3 @@ func serveLinksHandler(errorHandler influxdb.HTTPErrorHandler) http.Handler { } return http.HandlerFunc(fn) } - -func serveFlagsHandler(errorHandler influxdb.HTTPErrorHandler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - var ( - ctx = r.Context() - flags = feature.ExposedFlagsFromContext(ctx) - ) - if err := encodeResponse(ctx, w, http.StatusOK, flags); err != nil { - errorHandler.HandleHTTPError(ctx, err, w) - } - } - return http.HandlerFunc(fn) -} diff --git a/kit/feature/feature.go b/kit/feature/feature.go index ba1f1ed91e..7a84c3c23c 100644 --- a/kit/feature/feature.go +++ b/kit/feature/feature.go @@ -51,18 +51,19 @@ func FlagsFromContext(ctx context.Context) map[string]interface{} { 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) map[string]interface{} { +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 := byKey[k]; flag != nil && flag.Expose() { + if flag, found := byKey(k); found && flag.Expose() { filtered[k] = v } } @@ -131,3 +132,9 @@ func (*defaultFlagger) Flags(_ context.Context, flags ...Flag) (map[string]inter 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 +} diff --git a/kit/feature/middleware.go b/kit/feature/middleware.go index 675dd56eb0..0c60c40978 100644 --- a/kit/feature/middleware.go +++ b/kit/feature/middleware.go @@ -1,8 +1,10 @@ package feature import ( + "encoding/json" "net/http" + "github.com/influxdata/influxdb/v2" "go.uber.org/zap" ) @@ -41,3 +43,20 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.next.ServeHTTP(w, r) } } + +// NewFlagsHandler returns a handler that returns the map of computed feature flags on the request context. +func NewFlagsHandler(errorHandler influxdb.HTTPErrorHandler, byKey ByKeyFn) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + + var ( + ctx = r.Context() + flags = ExposedFlagsFromContext(ctx, byKey) + ) + if err := json.NewEncoder(w).Encode(flags); err != nil { + errorHandler.HandleHTTPError(ctx, err, w) + } + } + return http.HandlerFunc(fn) +} diff --git a/kit/feature/override/override.go b/kit/feature/override/override.go index 811709893d..d489db7506 100644 --- a/kit/feature/override/override.go +++ b/kit/feature/override/override.go @@ -20,7 +20,7 @@ func Make(m map[string]string) (Flagger, error) { }, nil } -// Flags returns a map of default values. It never returns an error. +// Flags returns a map of default values with overrides applied. It never returns an error. func (f Flagger) Flags(_ context.Context, flags ...feature.Flag) (map[string]interface{}, error) { if len(flags) == 0 { flags = feature.Flags() diff --git a/kit/feature/target.go b/kit/feature/target.go deleted file mode 100644 index 41771ed28e..0000000000 --- a/kit/feature/target.go +++ /dev/null @@ -1,68 +0,0 @@ -package feature - -import ( - "context" - "errors" - "fmt" - - "github.com/influxdata/influxdb/v2" - icontext "github.com/influxdata/influxdb/v2/context" -) - -var ErrMissingTargetInfo = errors.New("unable to determine any user or org IDs from authorizer on context") - -// Target against which to match a feature flag rule. -type Target struct { - // UserID to Target. - UserID influxdb.ID - // OrgIDs to Target. - OrgIDs []influxdb.ID -} - -// MakeTarget returns a populated feature flag Target for the given environment, -// including user and org information from the provided context, if available. -// -// If the authorizer on the context provides a user ID, it is used to fetch associated org IDs. -// If a user ID is not provided, an org ID is taken directly off the authorizer if possible. -// If no user or org information can be determined, a sentinel error is returned. -func MakeTarget(ctx context.Context, urms influxdb.UserResourceMappingService) (Target, error) { - auth, err := icontext.GetAuthorizer(ctx) - if err != nil { - return Target{}, ErrMissingTargetInfo - } - userID := auth.GetUserID() - - var orgIDs []influxdb.ID - if userID.Valid() { - orgIDs, err = fromURMs(ctx, userID, urms) - if err != nil { - return Target{}, err - } - } else if a, ok := auth.(*influxdb.Authorization); ok { - orgIDs = []influxdb.ID{a.OrgID} - } else { - return Target{}, ErrMissingTargetInfo - } - - return Target{ - UserID: userID, - OrgIDs: orgIDs, - }, nil -} - -func fromURMs(ctx context.Context, userID influxdb.ID, urms influxdb.UserResourceMappingService) ([]influxdb.ID, error) { - m, _, err := urms.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{ - UserID: userID, - ResourceType: influxdb.OrgsResourceType, - }) - if err != nil { - return nil, fmt.Errorf("finding organization mappings for user %s: %v", userID, err) - } - - orgIDs := make([]influxdb.ID, 0, len(m)) - for _, o := range m { - orgIDs = append(orgIDs, o.ResourceID) - } - - return orgIDs, nil -}