diff --git a/package.json b/package.json index c33de69bf67..d8fe5451d99 100644 --- a/package.json +++ b/package.json @@ -312,6 +312,7 @@ "@locker/near-membrane-shared-dom": "0.14.0", "@msagl/core": "^1.1.19", "@msagl/parser": "^1.1.19", + "@openfeature/ofrep-web-provider": "^0.3.3", "@openfeature/web-sdk": "^1.6.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/exporter-collector": "0.25.0", diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 29079be8bd8..11592e20ae3 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -316,6 +316,7 @@ export interface GrafanaConfig { quickRanges?: TimeOption[]; pluginRestrictedAPIsAllowList?: Record; pluginRestrictedAPIsBlockList?: Record; + openFeatureContext: Record; // The namespace to use for kubernetes apiserver requests namespace: string; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 2576de932b2..2b816dfe1ad 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -259,6 +259,8 @@ export class GrafanaBootConfig { listDashboardScopesEndpoint = ''; listScopesEndpoint = ''; + openFeatureContext: Record = {}; + constructor( options: BootData['settings'] & { bootData: BootData; diff --git a/packages/grafana-runtime/src/internal/openFeature/index.ts b/packages/grafana-runtime/src/internal/openFeature/index.ts index 891eefe7958..57e35516e30 100644 --- a/packages/grafana-runtime/src/internal/openFeature/index.ts +++ b/packages/grafana-runtime/src/internal/openFeature/index.ts @@ -24,7 +24,7 @@ export async function initOpenFeature() { await OpenFeature.setProviderAndWait(ofProvider, { targetingKey: config.namespace, - namespace: config.namespace, + ...config.openFeatureContext, }); } diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index dd1313de717..296ab669aa2 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -305,6 +305,7 @@ type FrontendSettingsDTO struct { LocalFileSystemAvailable bool `json:"localFileSystemAvailable"` // Experimental Scope settings - ListScopesEndpoint string `json:"listScopesEndpoint"` - ListDashboardScopesEndpoint string `json:"listDashboardScopesEndpoint"` + ListScopesEndpoint string `json:"listScopesEndpoint"` + ListDashboardScopesEndpoint string `json:"listDashboardScopesEndpoint"` + OpenFeatureContext map[string]string `json:"openFeatureContext"` } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 7b8230df5a3..43efa380f5c 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -350,6 +350,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro MaxIdleConns: hs.Cfg.SqlDatasourceMaxIdleConnsDefault, ConnMaxLifetime: hs.Cfg.SqlDatasourceMaxConnLifetimeDefault, }, + OpenFeatureContext: hs.Cfg.OpenFeature.ContextAttrs, } if hs.Cfg.UnifiedAlerting.StateHistory.Enabled { diff --git a/pkg/services/featuremgmt/openfeature.go b/pkg/services/featuremgmt/openfeature.go index 9a47e76a6ba..001809c9e56 100644 --- a/pkg/services/featuremgmt/openfeature.go +++ b/pkg/services/featuremgmt/openfeature.go @@ -40,7 +40,11 @@ func InitOpenFeatureWithCfg(cfg *setting.Cfg) error { if err != nil { return fmt.Errorf("failed to initialize OpenFeature: %w", err) } - openfeature.SetEvaluationContext(openfeature.NewEvaluationContext(cfg.OpenFeature.TargetingKey, cfg.OpenFeature.ContextAttrs)) + contextAttrs := make(map[string]any) + for k, v := range cfg.OpenFeature.ContextAttrs { + contextAttrs[k] = v + } + openfeature.SetEvaluationContext(openfeature.NewEvaluationContext(cfg.OpenFeature.TargetingKey, contextAttrs)) return nil } diff --git a/pkg/setting/setting_openfeature.go b/pkg/setting/setting_openfeature.go index a817dc28a80..52966b0f7ba 100644 --- a/pkg/setting/setting_openfeature.go +++ b/pkg/setting/setting_openfeature.go @@ -15,7 +15,7 @@ type OpenFeatureSettings struct { ProviderType string URL *url.URL TargetingKey string - ContextAttrs map[string]any + ContextAttrs map[string]string } func (cfg *Cfg) readOpenFeatureSettings() error { @@ -24,10 +24,15 @@ func (cfg *Cfg) readOpenFeatureSettings() error { config := cfg.Raw.Section("feature_toggles.openfeature") cfg.OpenFeature.APIEnabled = config.Key("enable_api").MustBool(true) cfg.OpenFeature.ProviderType = config.Key("provider").MustString(StaticProviderType) - cfg.OpenFeature.TargetingKey = config.Key("targetingKey").MustString(cfg.AppURL) - strURL := config.Key("url").MustString("") + defaultTargetingKey := "default" + if cfg.StackID != "" { + defaultTargetingKey = fmt.Sprintf("stacks-%s", cfg.StackID) + } + + cfg.OpenFeature.TargetingKey = config.Key("targetingKey").MustString(defaultTargetingKey) + if strURL != "" && cfg.OpenFeature.ProviderType == GOFFProviderType { u, err := url.Parse(strURL) if err != nil { @@ -38,7 +43,7 @@ func (cfg *Cfg) readOpenFeatureSettings() error { // build the eval context attributes using [feature_toggles.openfeature.context] section ctxConf := cfg.Raw.Section("feature_toggles.openfeature.context") - attrs := map[string]any{} + attrs := map[string]string{} for _, key := range ctxConf.KeyStrings() { attrs[key] = ctxConf.Key(key).String() } @@ -48,6 +53,10 @@ func (cfg *Cfg) readOpenFeatureSettings() error { attrs["grafana_version"] = BuildVersion } + if _, ok := attrs["namespace"]; !ok { + attrs["namespace"] = defaultTargetingKey + } + cfg.OpenFeature.ContextAttrs = attrs return nil } diff --git a/pkg/setting/setting_openfeature_test.go b/pkg/setting/setting_openfeature_test.go index f03db2f079a..394b68f6568 100644 --- a/pkg/setting/setting_openfeature_test.go +++ b/pkg/setting/setting_openfeature_test.go @@ -11,12 +11,13 @@ func Test_CtxAttrs(t *testing.T) { testCases := []struct { name string conf string - expected map[string]any + expected map[string]string }{ { name: "empty config - only default attributes should be present", - expected: map[string]any{ + expected: map[string]string{ "grafana_version": "", + "namespace": "default", }, }, { @@ -26,11 +27,12 @@ func Test_CtxAttrs(t *testing.T) { foo = bar baz = qux quux = corge`, - expected: map[string]any{ + expected: map[string]string{ "foo": "bar", "baz": "qux", "quux": "corge", "grafana_version": "", + "namespace": "default", }, }, { @@ -39,9 +41,10 @@ quux = corge`, [feature_toggles.openfeature.context] grafana_version = 10.0.0 foo = bar`, - expected: map[string]any{ + expected: map[string]string{ "grafana_version": "10.0.0", "foo": "bar", + "namespace": "default", }, }, } diff --git a/yarn.lock b/yarn.lock index 4aad46d0157..820778b8c7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18308,6 +18308,7 @@ __metadata: "@msagl/core": "npm:^1.1.19" "@msagl/parser": "npm:^1.1.19" "@npmcli/package-json": "npm:^6.0.0" + "@openfeature/ofrep-web-provider": "npm:^0.3.3" "@openfeature/web-sdk": "npm:^1.6.1" "@opentelemetry/api": "npm:1.9.0" "@opentelemetry/exporter-collector": "npm:0.25.0"