mirror of https://github.com/grafana/grafana.git
130 lines
3.7 KiB
Go
130 lines
3.7 KiB
Go
package featuremgmt
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
clientauthmiddleware "github.com/grafana/grafana/pkg/clientauth/middleware"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
|
"github.com/open-feature/go-sdk/openfeature"
|
|
)
|
|
|
|
const (
|
|
featuresProviderAudience = "features.grafana.app"
|
|
)
|
|
|
|
// OpenFeatureConfig holds configuration for initializing OpenFeature
|
|
type OpenFeatureConfig struct {
|
|
// ProviderType is either "static" or "goff"
|
|
ProviderType string
|
|
// URL is the GOFF service URL (required for GOFF provider)
|
|
URL *url.URL
|
|
// HTTPClient is a pre-configured HTTP client (optional, used for GOFF provider)
|
|
HTTPClient *http.Client
|
|
// StaticFlags are the feature flags to use with static provider
|
|
StaticFlags map[string]bool
|
|
// TargetingKey is used for evaluation context
|
|
TargetingKey string
|
|
// ContextAttrs are additional attributes for evaluation context
|
|
ContextAttrs map[string]any
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
|
|
p, err := createProvider(config.ProviderType, config.URL, config.StaticFlags, config.HTTPClient)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = openfeature.SetProviderAndWait(p); err != nil {
|
|
return fmt.Errorf("failed to set global feature provider: %s, %w", config.ProviderType, err)
|
|
}
|
|
|
|
contextAttrs := make(map[string]any)
|
|
for k, v := range config.ContextAttrs {
|
|
contextAttrs[k] = v
|
|
}
|
|
openfeature.SetEvaluationContext(openfeature.NewEvaluationContext(config.TargetingKey, contextAttrs))
|
|
|
|
return nil
|
|
}
|
|
|
|
// InitOpenFeatureWithCfg initializes OpenFeature from setting.Cfg
|
|
func InitOpenFeatureWithCfg(cfg *setting.Cfg) error {
|
|
confFlags, err := setting.ReadFeatureTogglesFromInitFile(cfg.Raw.Section("feature_toggles"))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read feature flags from config: %w", err)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
httpcli, err = goffHTTPClient(m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
contextAttrs := make(map[string]any)
|
|
for k, v := range cfg.OpenFeature.ContextAttrs {
|
|
contextAttrs[k] = v
|
|
}
|
|
|
|
return InitOpenFeature(OpenFeatureConfig{
|
|
ProviderType: cfg.OpenFeature.ProviderType,
|
|
URL: cfg.OpenFeature.URL,
|
|
HTTPClient: httpcli,
|
|
StaticFlags: confFlags,
|
|
TargetingKey: cfg.OpenFeature.TargetingKey,
|
|
ContextAttrs: contextAttrs,
|
|
})
|
|
}
|
|
|
|
func createProvider(
|
|
providerType string,
|
|
u *url.URL,
|
|
staticFlags map[string]bool,
|
|
httpClient *http.Client,
|
|
) (openfeature.FeatureProvider, error) {
|
|
if providerType != setting.GOFFProviderType {
|
|
return newStaticProvider(staticFlags)
|
|
}
|
|
|
|
if u == nil || u.String() == "" {
|
|
return nil, fmt.Errorf("feature provider url is required for GOFFProviderType")
|
|
}
|
|
|
|
return newGOFFProvider(u.String(), httpClient)
|
|
}
|
|
|
|
func goffHTTPClient(m *clientauthmiddleware.TokenExchangeMiddleware) (*http.Client, error) {
|
|
httpcli, err := sdkhttpclient.NewProvider().New(sdkhttpclient.Options{
|
|
TLS: &sdkhttpclient.TLSOptions{InsecureSkipVerify: true},
|
|
Timeouts: &sdkhttpclient.TimeoutOptions{
|
|
Timeout: 10 * time.Second,
|
|
},
|
|
Middlewares: []sdkhttpclient.Middleware{
|
|
m.New([]string{featuresProviderAudience}),
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create http client for openfeature: %w", err)
|
|
}
|
|
|
|
return httpcli, nil
|
|
}
|