OpenFeature: Inject eval ctx into req ctx (#108017)

* OpenFeature: Inject eval ctx into req ctx

* Remove stackId

* add unit test

Signed-off-by: Dave Henderson <dave.henderson@grafana.com>

---------

Signed-off-by: Dave Henderson <dave.henderson@grafana.com>
Co-authored-by: Dave Henderson <dave.henderson@grafana.com>
This commit is contained in:
Tania
2025-07-11 21:26:44 +02:00
committed by GitHub
co-authored by Dave Henderson
parent f9f04c2a55
commit a33b634a9f
2 changed files with 60 additions and 0 deletions
@@ -6,10 +6,12 @@ import (
"fmt"
"net/http"
"github.com/open-feature/go-sdk/openfeature"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
claims "github.com/grafana/authlib/types"
authnClients "github.com/grafana/grafana/pkg/services/authn/clients"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -141,6 +143,16 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
reqContext.Resp.Before(h.addIDHeaderEndOfRequestFunc(reqContext.SignedInUser))
}
// Set open feature evaluation context with namespace
ns := "default"
if id != nil {
ns = id.Namespace
}
evalCtx := openfeature.NewEvaluationContext(ns, map[string]any{
"namespace": ns,
})
ctx = openfeature.MergeTransactionContext(ctx, evalCtx)
// End the span to make next handlers not wrapped within middleware span
span.End()
next.ServeHTTP(w, r.WithContext(ctx))
@@ -5,10 +5,12 @@ import (
"net/http"
"testing"
"github.com/open-feature/go-sdk/openfeature"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
@@ -209,4 +211,50 @@ func TestContextHandler(t *testing.T) {
require.NoError(t, res.Body.Close())
})
})
t.Run("openfeature evaluation context defaults", func(t *testing.T) {
handler := contexthandler.ProvideService(
setting.NewCfg(),
&authntest.FakeService{ExpectedErr: errors.New("some error")},
featuremgmt.WithFeatures(),
)
server := webtest.NewServer(t, routing.NewRouteRegister())
server.Mux.Use(handler.Middleware)
server.Mux.Get("/api/handler", func(c *contextmodel.ReqContext) {
evalCtx := openfeature.TransactionContext(c.Req.Context())
require.NotNil(t, evalCtx)
require.Equal(t, "default", evalCtx.Attribute("namespace"))
})
res, err := server.Send(server.NewGetRequest("/api/handler"))
require.NoError(t, err)
require.NoError(t, res.Body.Close())
})
t.Run("openfeature evaluation context with user namespace", func(t *testing.T) {
handler := contexthandler.ProvideService(
setting.NewCfg(),
&authntest.FakeService{
ExpectedIdentity: &authn.Identity{
ID: "1",
Type: claims.TypeUser,
Namespace: "org-3",
},
},
featuremgmt.WithFeatures(),
)
server := webtest.NewServer(t, routing.NewRouteRegister())
server.Mux.Use(handler.Middleware)
server.Mux.Get("/api/handler", func(c *contextmodel.ReqContext) {
evalCtx := openfeature.TransactionContext(c.Req.Context())
require.NotNil(t, evalCtx)
require.Equal(t, "org-3", evalCtx.Attribute("namespace"))
})
res, err := server.Send(server.NewGetRequest("/api/handler"))
require.NoError(t, err)
require.NoError(t, res.Body.Close())
})
}