Extend OpenFeature service (#106707)

This commit is contained in:
Tania
2025-06-13 17:30:53 +02:00
committed by GitHub
parent 18b0eec0a3
commit 1500fa5772
7 changed files with 265 additions and 102 deletions
+72 -31
View File
@@ -1,71 +1,112 @@
package featuremgmt
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/open-feature/go-sdk/openfeature"
)
const (
staticProviderType = "static"
goffProviderType = "goff"
configSectionName = "feature_toggles.openfeature"
contextSectionName = "feature_toggles.openfeature.context"
)
type OpenFeatureService struct {
cfg *setting.Cfg
log log.Logger
provider openfeature.FeatureProvider
Client openfeature.IClient
}
func ProvideOpenFeatureService(cfg *setting.Cfg) (*OpenFeatureService, error) {
conf := cfg.Raw.Section(configSectionName)
provType := conf.Key("provider").MustString(staticProviderType)
url := conf.Key("url").MustString("")
key := conf.Key("targetingKey").MustString(cfg.AppURL)
var provider openfeature.FeatureProvider
var err error
if provType == goffProviderType {
provider, err = newGOFFProvider(url)
if cfg.OpenFeature.ProviderType == setting.GOFFProviderType {
if cfg.OpenFeature.URL == nil {
return nil, fmt.Errorf("feature provider url is required for GOFFProviderType")
}
provider, err = newGOFFProvider(cfg.OpenFeature.URL.String())
} else {
provider, err = newStaticProvider(cfg)
}
if err != nil {
return nil, fmt.Errorf("failed to create %s feature provider: %w", provType, err)
return nil, fmt.Errorf("failed to create %s feature provider: %w", cfg.OpenFeature.ProviderType, err)
}
if err := openfeature.SetProviderAndWait(provider); err != nil {
return nil, fmt.Errorf("failed to set global %s feature provider: %w", provType, err)
return nil, fmt.Errorf("failed to set global %s feature provider: %w", cfg.OpenFeature.ProviderType, err)
}
attrs := ctxAttrs(cfg)
openfeature.SetEvaluationContext(openfeature.NewEvaluationContext(key, attrs))
openfeature.SetEvaluationContext(openfeature.NewEvaluationContext(cfg.OpenFeature.TargetingKey, cfg.OpenFeature.ContextAttrs))
client := openfeature.NewClient("grafana-openfeature-client")
return &OpenFeatureService{
cfg: cfg,
log: log.New("openfeatureservice"),
provider: provider,
Client: client,
}, nil
}
// ctxAttrs uses config.ini [feature_toggles.openfeature.context] section to build the eval context attributes
func ctxAttrs(cfg *setting.Cfg) map[string]any {
ctxConf := cfg.Raw.Section(contextSectionName)
attrs := map[string]any{}
for _, key := range ctxConf.KeyStrings() {
attrs[key] = ctxConf.Key(key).String()
func (s *OpenFeatureService) EvalFlagWithStaticProvider(ctx context.Context, flagKey string) (openfeature.BooleanEvaluationDetails, error) {
_, ok := s.provider.(*inMemoryBulkProvider)
if !ok {
return openfeature.BooleanEvaluationDetails{}, fmt.Errorf("not a static provider, request must be sent to open feature service")
}
// Some default attributes
if _, ok := attrs["grafana_version"]; !ok {
attrs["grafana_version"] = setting.BuildVersion
result, err := s.Client.BooleanValueDetails(ctx, flagKey, false, openfeature.TransactionContext(ctx))
if err != nil {
return openfeature.BooleanEvaluationDetails{}, fmt.Errorf("failed to evaluate flag %s: %w", flagKey, err)
}
return attrs
return result, nil
}
func (s *OpenFeatureService) EvalAllFlagsWithStaticProvider(ctx context.Context) (OFREPBulkResponse, error) {
p, ok := s.provider.(*inMemoryBulkProvider)
if !ok {
return OFREPBulkResponse{}, fmt.Errorf("not a static provider, request must be sent to open feature service")
}
flags, err := p.ListFlags()
if err != nil {
return OFREPBulkResponse{}, fmt.Errorf("static provider failed to list all flags: %w", err)
}
allFlags := make([]OFREPFlag, 0, len(flags))
for _, flagKey := range flags {
result, err := s.Client.BooleanValueDetails(ctx, flagKey, false, openfeature.TransactionContext(ctx))
if err != nil {
s.log.Error("failed to evaluate flag during bulk evaluation", "flagKey", flagKey, "error", err)
continue
}
allFlags = append(allFlags, OFREPFlag{
Key: flagKey,
Value: result.Value,
Reason: "static provider evaluation result",
Variant: result.Variant,
ErrorCode: string(result.ErrorCode),
ErrorDetails: result.ErrorMessage,
})
}
return OFREPBulkResponse{Flags: allFlags}, nil
}
// Bulk evaluation response
type OFREPBulkResponse struct {
Flags []OFREPFlag `json:"flags"`
Metadata map[string]any `json:"metadata,omitempty"`
}
type OFREPFlag struct {
Key string `json:"key"`
Value bool `json:"value"`
Reason string `json:"reason"`
Variant string `json:"variant,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
ErrorDetails string `json:"errorDetails,omitempty"`
}