OpenFeature: Add OFREP provider type (#115857)
Add new OFREP provider for OpenFeature
This commit is contained in:
@@ -147,6 +147,7 @@ require (
|
||||
github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group
|
||||
github.com/open-feature/go-sdk v1.16.0 // @grafana/grafana-backend-group
|
||||
github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.6 // @grafana/grafana-backend-group
|
||||
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.6 // @grafana/grafana-backend-group
|
||||
github.com/openfga/api/proto v0.0.0-20250909172242-b4b2a12f5c67 // @grafana/identity-access-team
|
||||
github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20251027165255-0f8f255e5f6c // @grafana/identity-access-team
|
||||
github.com/openfga/openfga v1.11.1 // @grafana/identity-access-team
|
||||
@@ -544,7 +545,6 @@ require (
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.1 // indirect
|
||||
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.6 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.124.1 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.124.1 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.124.1 // indirect
|
||||
|
||||
@@ -276,7 +276,7 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if b.providerType == setting.GOFFProviderType {
|
||||
if b.providerType == setting.GOFFProviderType || b.providerType == setting.OFREPProviderType {
|
||||
b.proxyFlagReq(ctx, flagKey, isAuthedReq, w, r)
|
||||
return
|
||||
}
|
||||
@@ -304,7 +304,7 @@ func (b *APIBuilder) allFlagsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
isAuthedReq := b.isAuthenticatedRequest(r)
|
||||
span.SetAttributes(attribute.Bool("authenticated", isAuthedReq))
|
||||
|
||||
if b.providerType == setting.GOFFProviderType {
|
||||
if b.providerType == setting.GOFFProviderType || b.providerType == setting.OFREPProviderType {
|
||||
b.proxyAllFlagReq(ctx, isAuthedReq, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package featuremgmt
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
ofrep "github.com/open-feature/go-sdk-contrib/providers/ofrep"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
func newOFREPProvider(url string, client *http.Client) (openfeature.FeatureProvider, error) {
|
||||
options := []ofrep.Option{}
|
||||
if client != nil {
|
||||
options = append(options, ofrep.WithClient(client))
|
||||
}
|
||||
|
||||
return ofrep.NewProvider(url, options...), nil
|
||||
}
|
||||
@@ -19,11 +19,11 @@ const (
|
||||
|
||||
// OpenFeatureConfig holds configuration for initializing OpenFeature
|
||||
type OpenFeatureConfig struct {
|
||||
// ProviderType is either "static" or "goff"
|
||||
// ProviderType is either "static", "goff", or "ofrep"
|
||||
ProviderType string
|
||||
// URL is the GOFF service URL (required for GOFF provider)
|
||||
// URL is the GOFF or OFREP service URL (required for GOFF + OFREP providers)
|
||||
URL *url.URL
|
||||
// HTTPClient is a pre-configured HTTP client (optional, used for GOFF provider)
|
||||
// HTTPClient is a pre-configured HTTP client (optional, used for GOFF + OFREP providers)
|
||||
HTTPClient *http.Client
|
||||
// StaticFlags are the feature flags to use with static provider
|
||||
StaticFlags map[string]bool
|
||||
@@ -35,9 +35,9 @@ type OpenFeatureConfig struct {
|
||||
|
||||
// InitOpenFeature initializes OpenFeature with the provided configuration
|
||||
func InitOpenFeature(config OpenFeatureConfig) error {
|
||||
// For GOFF provider, ensure we have a URL
|
||||
if config.ProviderType == setting.GOFFProviderType && (config.URL == nil || config.URL.String() == "") {
|
||||
return fmt.Errorf("URL is required for GOFF provider")
|
||||
// For GOFF + OFREP providers, ensure we have a URL
|
||||
if (config.ProviderType == setting.GOFFProviderType || config.ProviderType == setting.OFREPProviderType) && (config.URL == nil || config.URL.String() == "") {
|
||||
return fmt.Errorf("URL is required for GOFF + OFREP providers")
|
||||
}
|
||||
|
||||
p, err := createProvider(config.ProviderType, config.URL, config.StaticFlags, config.HTTPClient)
|
||||
@@ -66,13 +66,17 @@ func InitOpenFeatureWithCfg(cfg *setting.Cfg) error {
|
||||
}
|
||||
|
||||
var httpcli *http.Client
|
||||
if cfg.OpenFeature.ProviderType == setting.GOFFProviderType {
|
||||
m, err := clientauthmiddleware.NewTokenExchangeMiddleware(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create token exchange middleware: %w", err)
|
||||
if cfg.OpenFeature.ProviderType == setting.GOFFProviderType || cfg.OpenFeature.ProviderType == setting.OFREPProviderType {
|
||||
var m *clientauthmiddleware.TokenExchangeMiddleware
|
||||
|
||||
if cfg.OpenFeature.ProviderType == setting.GOFFProviderType {
|
||||
m, err = clientauthmiddleware.NewTokenExchangeMiddleware(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create token exchange middleware: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
httpcli, err = goffHTTPClient(m)
|
||||
httpcli, err = createHTTPClient(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,28 +103,35 @@ func createProvider(
|
||||
staticFlags map[string]bool,
|
||||
httpClient *http.Client,
|
||||
) (openfeature.FeatureProvider, error) {
|
||||
if providerType != setting.GOFFProviderType {
|
||||
return newStaticProvider(staticFlags)
|
||||
if providerType == setting.GOFFProviderType || providerType == setting.OFREPProviderType {
|
||||
if u == nil || u.String() == "" {
|
||||
return nil, fmt.Errorf("feature provider url is required for GOFFProviderType + OFREPProviderType")
|
||||
}
|
||||
|
||||
if providerType == setting.GOFFProviderType {
|
||||
return newGOFFProvider(u.String(), httpClient)
|
||||
}
|
||||
|
||||
if providerType == setting.OFREPProviderType {
|
||||
return newOFREPProvider(u.String(), httpClient)
|
||||
}
|
||||
}
|
||||
|
||||
if u == nil || u.String() == "" {
|
||||
return nil, fmt.Errorf("feature provider url is required for GOFFProviderType")
|
||||
}
|
||||
|
||||
return newGOFFProvider(u.String(), httpClient)
|
||||
return newStaticProvider(staticFlags)
|
||||
}
|
||||
|
||||
func goffHTTPClient(m *clientauthmiddleware.TokenExchangeMiddleware) (*http.Client, error) {
|
||||
httpcli, err := sdkhttpclient.NewProvider().New(sdkhttpclient.Options{
|
||||
func createHTTPClient(m *clientauthmiddleware.TokenExchangeMiddleware) (*http.Client, error) {
|
||||
options := sdkhttpclient.Options{
|
||||
TLS: &sdkhttpclient.TLSOptions{InsecureSkipVerify: true},
|
||||
Timeouts: &sdkhttpclient.TimeoutOptions{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
Middlewares: []sdkhttpclient.Middleware{
|
||||
m.New([]string{featuresProviderAudience}),
|
||||
},
|
||||
})
|
||||
}
|
||||
if m != nil {
|
||||
options.Middlewares = append(options.Middlewares, m.New([]string{featuresProviderAudience}))
|
||||
}
|
||||
|
||||
httpcli, err := sdkhttpclient.NewProvider().New(options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create http client for openfeature: %w", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
gofeatureflag "github.com/open-feature/go-sdk-contrib/providers/go-feature-flag/pkg"
|
||||
ofrep "github.com/open-feature/go-sdk-contrib/providers/ofrep"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -60,6 +61,15 @@ func TestCreateProvider(t *testing.T) {
|
||||
expectedProvider: setting.GOFFProviderType,
|
||||
failSigning: true,
|
||||
},
|
||||
{
|
||||
name: "ofrep provider",
|
||||
cfg: setting.OpenFeatureSettings{
|
||||
ProviderType: setting.OFREPProviderType,
|
||||
URL: u,
|
||||
TargetingKey: "grafana",
|
||||
},
|
||||
expectedProvider: setting.OFREPProviderType,
|
||||
},
|
||||
{
|
||||
name: "invalid provider",
|
||||
cfg: setting.OpenFeatureSettings{
|
||||
@@ -96,20 +106,24 @@ func TestCreateProvider(t *testing.T) {
|
||||
}
|
||||
|
||||
tokenExchangeMiddleware := middleware.TestingTokenExchangeMiddleware(tokenExchangeClient)
|
||||
goffClient, err := goffHTTPClient(tokenExchangeMiddleware)
|
||||
httpClient, err := createHTTPClient(tokenExchangeMiddleware)
|
||||
require.NoError(t, err, "failed to create goff http client")
|
||||
provider, err := createProvider(tc.cfg.ProviderType, tc.cfg.URL, nil, goffClient)
|
||||
provider, err := createProvider(tc.cfg.ProviderType, tc.cfg.URL, nil, httpClient)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = openfeature.SetProviderAndWait(provider)
|
||||
require.NoError(t, err, "failed to set provider")
|
||||
|
||||
if tc.expectedProvider == setting.GOFFProviderType {
|
||||
switch tc.expectedProvider {
|
||||
case setting.GOFFProviderType:
|
||||
_, ok := provider.(*gofeatureflag.Provider)
|
||||
assert.True(t, ok, "expected provider to be of type goff.Provider")
|
||||
|
||||
testGoFFProvider(t, tc.failSigning)
|
||||
} else {
|
||||
case setting.OFREPProviderType:
|
||||
_, ok := provider.(*ofrep.Provider)
|
||||
assert.True(t, ok, "expected provider to be of type ofrep.Provider")
|
||||
default:
|
||||
_, ok := provider.(*inMemoryBulkProvider)
|
||||
assert.True(t, ok, "expected provider to be of type memprovider.InMemoryProvider")
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
const (
|
||||
StaticProviderType = "static"
|
||||
GOFFProviderType = "goff"
|
||||
OFREPProviderType = "ofrep"
|
||||
)
|
||||
|
||||
type OpenFeatureSettings struct {
|
||||
@@ -33,7 +34,7 @@ func (cfg *Cfg) readOpenFeatureSettings() error {
|
||||
|
||||
cfg.OpenFeature.TargetingKey = config.Key("targetingKey").MustString(defaultTargetingKey)
|
||||
|
||||
if strURL != "" && cfg.OpenFeature.ProviderType == GOFFProviderType {
|
||||
if strURL != "" && (cfg.OpenFeature.ProviderType == GOFFProviderType || cfg.OpenFeature.ProviderType == OFREPProviderType) {
|
||||
u, err := url.Parse(strURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid feature provider url: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user