FeatureFlags: Use interface rather than manager (#80000)

pull/79960/head^2
Ryan McKinley 1 year ago committed by GitHub
parent e550829dae
commit 1caaa56de0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      pkg/api/annotations.go
  2. 2
      pkg/api/common_test.go
  3. 2
      pkg/api/datasources.go
  4. 8
      pkg/api/featuremgmt.go
  5. 31
      pkg/api/featuremgmt_test.go
  6. 4
      pkg/api/folder_bench_test.go
  7. 2
      pkg/api/folder_test.go
  8. 2
      pkg/api/frontendsettings_test.go
  9. 6
      pkg/api/http_server.go
  10. 2
      pkg/infra/usagestats/statscollector/service_test.go
  11. 4
      pkg/login/social/connectors/azuread_oauth.go
  12. 4
      pkg/login/social/connectors/generic_oauth.go
  13. 4
      pkg/login/social/connectors/github_oauth.go
  14. 4
      pkg/login/social/connectors/gitlab_oauth.go
  15. 4
      pkg/login/social/connectors/google_oauth.go
  16. 4
      pkg/login/social/connectors/grafana_com_oauth.go
  17. 4
      pkg/login/social/connectors/okta_oauth.go
  18. 4
      pkg/login/social/connectors/social_base.go
  19. 4
      pkg/login/social/socialimpl/service.go
  20. 2
      pkg/login/social/socialimpl/service_test.go
  21. 6
      pkg/plugins/config/config.go
  22. 27
      pkg/plugins/envvars/envvars_test.go
  23. 5
      pkg/plugins/ifaces.go
  24. 23
      pkg/plugins/manager/fakes/fakes.go
  25. 4
      pkg/plugins/manager/loader/finder/local.go
  26. 6
      pkg/services/accesscontrol/acimpl/service.go
  27. 4
      pkg/services/accesscontrol/api/api.go
  28. 4
      pkg/services/authn/clients/session.go
  29. 2
      pkg/services/authn/clients/session_test.go
  30. 4
      pkg/services/contexthandler/contexthandler.go
  31. 6
      pkg/services/featuremgmt/manager.go
  32. 4
      pkg/services/featuremgmt/manager_test.go
  33. 4
      pkg/services/featuremgmt/models.go
  34. 2
      pkg/services/featuremgmt/usage_stats_test.go
  35. 2
      pkg/services/folder/folderimpl/folder_test.go
  36. 4
      pkg/services/navtree/navtreeimpl/navtree.go
  37. 4
      pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go
  38. 2
      pkg/services/pluginsintegration/config/config.go
  39. 4
      pkg/services/pluginsintegration/loader/loader_test.go
  40. 3
      pkg/services/pluginsintegration/pipeline/steps_test.go
  41. 6
      pkg/services/publicdashboards/api/api.go
  42. 4
      pkg/services/secrets/kvstore/test_helpers.go
  43. 2
      pkg/services/secrets/manager/helpers.go
  44. 2
      pkg/services/serviceaccounts/extsvcaccounts/service.go
  45. 5
      pkg/services/serviceaccounts/extsvcaccounts/service_test.go
  46. 8
      pkg/services/sqlstore/permissions/dashboard_test.go
  47. 2
      pkg/services/ssosettings/ssosettingsimpl/service.go
  48. 20
      pkg/tsdb/influxdb/mocks_test.go

@ -600,7 +600,7 @@ func (hs *HTTPServer) GetAnnotationTags(c *contextmodel.ReqContext) response.Res
// where <type> is the type of annotation with id <id>.
// If annotationPermissionUpdate feature toggle is enabled, dashboard annotation scope will be resolved to the corresponding
// dashboard and folder scopes (eg, "dashboards:uid:<annotation_dashboard_uid>", "folders:uid:<parent_folder_uid>" etc).
func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository, features *featuremgmt.FeatureManager, dashSvc dashboards.DashboardService, folderSvc folder.Service) (string, accesscontrol.ScopeAttributeResolver) {
func AnnotationTypeScopeResolver(annotationsRepo annotations.Repository, features featuremgmt.FeatureToggles, dashSvc dashboards.DashboardService, folderSvc folder.Service) (string, accesscontrol.ScopeAttributeResolver) {
prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("")
return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) {
scopeParts := strings.Split(initialScope, ":")

@ -258,7 +258,7 @@ func userWithPermissions(orgID int64, permissions []accesscontrol.Permission) *u
return &user.SignedInUser{IsAnonymous: true, OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(permissions)}}
}
func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
func setupSimpleHTTPServer(features featuremgmt.FeatureToggles) *HTTPServer {
if features == nil {
features = featuremgmt.WithFeatures()
}

@ -339,7 +339,7 @@ func validateURL(cmdType string, url string) response.Response {
// validateJSONData prevents the user from adding a custom header with name that matches the auth proxy header name.
// This is done to prevent data source proxy from being used to circumvent auth proxy.
// For more context take a look at CVE-2022-35957
func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setting.Cfg, features *featuremgmt.FeatureManager) error {
func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setting.Cfg, features featuremgmt.FeatureToggles) error {
if jsonData == nil {
return nil
}

@ -25,7 +25,7 @@ func (hs *HTTPServer) GetFeatureToggles(ctx *contextmodel.ReqContext) response.R
dtos := make([]featuremgmt.FeatureToggleDTO, 0)
// loop through features an add features that should be visible to dtos
for _, ft := range hs.Features.GetFlags() {
for _, ft := range hs.featureManager.GetFlags() {
if isFeatureHidden(ft, cfg.HiddenToggles) {
continue
}
@ -67,7 +67,7 @@ func (hs *HTTPServer) UpdateFeatureToggle(ctx *contextmodel.ReqContext) response
for _, t := range cmd.FeatureToggles {
// make sure flag exists, and only continue if flag is writeable
if f, ok := hs.Features.LookupFlag(t.Name); ok && isFeatureWriteable(f, hs.Cfg.FeatureManagement.ReadOnlyToggles) {
if f, ok := hs.featureManager.LookupFlag(t.Name); ok && isFeatureWriteable(f, hs.Cfg.FeatureManagement.ReadOnlyToggles) {
hs.log.Info("UpdateFeatureToggle: updating toggle", "toggle_name", t.Name, "enabled", t.Enabled, "username", ctx.SignedInUser.Login)
payload.FeatureToggles[t.Name] = strconv.FormatBool(t.Enabled)
} else {
@ -82,13 +82,13 @@ func (hs *HTTPServer) UpdateFeatureToggle(ctx *contextmodel.ReqContext) response
return response.Respond(http.StatusBadRequest, "Failed to perform webhook request")
}
hs.Features.SetRestartRequired()
hs.featureManager.SetRestartRequired()
return response.Respond(http.StatusOK, "feature toggles updated successfully")
}
func (hs *HTTPServer) GetFeatureMgmtState(ctx *contextmodel.ReqContext) response.Response {
fmState := hs.Features.GetState()
fmState := hs.featureManager.GetState()
return response.Respond(http.StatusOK, fmState)
}

@ -8,6 +8,9 @@ import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@ -16,8 +19,6 @@ import (
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetFeatureToggles(t *testing.T) {
@ -405,13 +406,16 @@ func runGetScenario(
cfg := setting.NewCfg()
cfg.FeatureManagement = settings
fm := featuremgmt.WithFeatureFlags(append([]*featuremgmt.FeatureFlag{{
Name: featuremgmt.FlagFeatureToggleAdminPage,
Enabled: true,
Stage: featuremgmt.FeatureStageGeneralAvailability,
}}, features...))
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg
hs.Features = featuremgmt.WithFeatureFlags(append([]*featuremgmt.FeatureFlag{{
Name: featuremgmt.FlagFeatureToggleAdminPage,
Enabled: true,
Stage: featuremgmt.FeatureStageGeneralAvailability,
}}, features...))
hs.Features = fm
hs.featureManager = fm
hs.orgService = orgtest.NewOrgServiceFake()
hs.userService = &usertest.FakeUserService{
ExpectedUser: &user.User{ID: 1},
@ -469,13 +473,16 @@ func runSetScenario(
cfg := setting.NewCfg()
cfg.FeatureManagement = settings
features := featuremgmt.WithFeatureFlags(append([]*featuremgmt.FeatureFlag{{
Name: featuremgmt.FlagFeatureToggleAdminPage,
Enabled: true,
Stage: featuremgmt.FeatureStageGeneralAvailability,
}}, serverFeatures...))
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg
hs.Features = featuremgmt.WithFeatureFlags(append([]*featuremgmt.FeatureFlag{{
Name: featuremgmt.FlagFeatureToggleAdminPage,
Enabled: true,
Stage: featuremgmt.FeatureStageGeneralAvailability,
}}, serverFeatures...))
hs.Features = features
hs.featureManager = features
hs.orgService = orgtest.NewOrgServiceFake()
hs.userService = &usertest.FakeUserService{
ExpectedUser: &user.User{ID: 1},

@ -95,7 +95,7 @@ func BenchmarkFolderListAndSearch(b *testing.B) {
desc string
url string
expectedLen int
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
}{
{
desc: "impl=default nested_folders=on get root folders",
@ -423,7 +423,7 @@ func setupDB(b testing.TB) benchScenario {
}
}
func setupServer(b testing.TB, sc benchScenario, features *featuremgmt.FeatureManager) *web.Macaron {
func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureToggles) *web.Macaron {
b.Helper()
m := web.New()

@ -435,7 +435,7 @@ func TestFolderGetAPIEndpoint(t *testing.T) {
type testCase struct {
description string
URL string
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
expectedCode int
expectedParentUIDs []string
expectedParentOrgIDs []int64

@ -32,7 +32,7 @@ import (
"github.com/grafana/grafana/pkg/web"
)
func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt.FeatureManager, pstore pluginstore.Store, psettings pluginsettings.Service) (*web.Mux, *HTTPServer) {
func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.FeatureToggles, pstore pluginstore.Store, psettings pluginsettings.Service) (*web.Mux, *HTTPServer) {
t.Helper()
db.InitTestDB(t)
// nolint:staticcheck

@ -126,7 +126,8 @@ type HTTPServer struct {
RouteRegister routing.RouteRegister
RenderService rendering.Service
Cfg *setting.Cfg
Features *featuremgmt.FeatureManager
Features featuremgmt.FeatureToggles
featureManager *featuremgmt.FeatureManager
SettingsProvider setting.Provider
HooksService *hooks.HooksService
navTreeService navtree.Service
@ -290,7 +291,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
ShortURLService: shortURLService,
QueryHistoryService: queryHistoryService,
CorrelationsService: correlationsService,
Features: features,
Features: features, // a read only view of the managers state
featureManager: features,
StorageService: storageService,
RemoteCacheService: remoteCache,
ProvisioningService: provisioningService,

@ -382,7 +382,7 @@ func createService(t testing.TB, cfg *setting.Cfg, store db.DB, statsService sta
store,
&mockSocial{},
&pluginstore.FakePluginStore{},
featuremgmt.WithFeatures("feature1", "feature2"),
featuremgmt.WithManager("feature1", "feature2"),
o.datasources,
httpclient.NewProvider(sdkhttpclient.ProviderOptions{Middlewares: []sdkhttpclient.Middleware{}}),
)

@ -72,10 +72,10 @@ type keySetJWKS struct {
jose.JSONWebKeySet
}
func NewAzureADProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager, cache remotecache.CacheStorage) *SocialAzureAD {
func NewAzureADProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles, cache remotecache.CacheStorage) *SocialAzureAD {
config := createOAuthConfig(info, cfg, social.AzureADProviderName)
provider := &SocialAzureAD{
SocialBase: newSocialBase(social.AzureADProviderName, config, info, cfg.AutoAssignOrgRole, *features),
SocialBase: newSocialBase(social.AzureADProviderName, config, info, cfg.AutoAssignOrgRole, features),
cache: cache,
allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]),
forceUseGraphAPI: MustBool(info.Extra[forceUseGraphAPIKey], false),

@ -45,10 +45,10 @@ type SocialGenericOAuth struct {
teamIds []string
}
func NewGenericOAuthProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager) *SocialGenericOAuth {
func NewGenericOAuthProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialGenericOAuth {
config := createOAuthConfig(info, cfg, social.GenericOAuthProviderName)
provider := &SocialGenericOAuth{
SocialBase: newSocialBase(social.GenericOAuthProviderName, config, info, cfg.AutoAssignOrgRole, *features),
SocialBase: newSocialBase(social.GenericOAuthProviderName, config, info, cfg.AutoAssignOrgRole, features),
teamsUrl: info.TeamsUrl,
emailAttributeName: info.EmailAttributeName,
emailAttributePath: info.EmailAttributePath,

@ -53,13 +53,13 @@ var (
"User is not a member of one of the required organizations. Please contact identity provider administrator."))
)
func NewGitHubProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager) *SocialGithub {
func NewGitHubProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialGithub {
teamIdsSplitted := util.SplitString(info.Extra[teamIdsKey])
teamIds := mustInts(teamIdsSplitted)
config := createOAuthConfig(info, cfg, social.GitHubProviderName)
provider := &SocialGithub{
SocialBase: newSocialBase(social.GitHubProviderName, config, info, cfg.AutoAssignOrgRole, *features),
SocialBase: newSocialBase(social.GitHubProviderName, config, info, cfg.AutoAssignOrgRole, features),
teamIds: teamIds,
allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]),
}

@ -52,10 +52,10 @@ type userData struct {
IsGrafanaAdmin *bool `json:"-"`
}
func NewGitLabProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager) *SocialGitlab {
func NewGitLabProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialGitlab {
config := createOAuthConfig(info, cfg, social.GitlabProviderName)
provider := &SocialGitlab{
SocialBase: newSocialBase(social.GitlabProviderName, config, info, cfg.AutoAssignOrgRole, *features),
SocialBase: newSocialBase(social.GitlabProviderName, config, info, cfg.AutoAssignOrgRole, features),
}
if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) {

@ -38,10 +38,10 @@ type googleUserData struct {
rawJSON []byte `json:"-"`
}
func NewGoogleProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager) *SocialGoogle {
func NewGoogleProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialGoogle {
config := createOAuthConfig(info, cfg, social.GoogleProviderName)
provider := &SocialGoogle{
SocialBase: newSocialBase(social.GoogleProviderName, config, info, cfg.AutoAssignOrgRole, *features),
SocialBase: newSocialBase(social.GoogleProviderName, config, info, cfg.AutoAssignOrgRole, features),
}
if strings.HasPrefix(info.ApiUrl, legacyAPIURL) {

@ -33,7 +33,7 @@ type OrgRecord struct {
Login string `json:"login"`
}
func NewGrafanaComProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager) *SocialGrafanaCom {
func NewGrafanaComProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialGrafanaCom {
// Override necessary settings
info.AuthUrl = cfg.GrafanaComURL + "/oauth2/authorize"
info.TokenUrl = cfg.GrafanaComURL + "/api/oauth2/token"
@ -41,7 +41,7 @@ func NewGrafanaComProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings
config := createOAuthConfig(info, cfg, social.GrafanaComProviderName)
provider := &SocialGrafanaCom{
SocialBase: newSocialBase(social.GrafanaComProviderName, config, info, cfg.AutoAssignOrgRole, *features),
SocialBase: newSocialBase(social.GrafanaComProviderName, config, info, cfg.AutoAssignOrgRole, features),
url: cfg.GrafanaComURL,
allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]),
}

@ -44,10 +44,10 @@ type OktaClaims struct {
Name string `json:"name"`
}
func NewOktaProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager) *SocialOkta {
func NewOktaProvider(info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles) *SocialOkta {
config := createOAuthConfig(info, cfg, social.OktaProviderName)
provider := &SocialOkta{
SocialBase: newSocialBase(social.OktaProviderName, config, info, cfg.AutoAssignOrgRole, *features),
SocialBase: newSocialBase(social.OktaProviderName, config, info, cfg.AutoAssignOrgRole, features),
}
if info.UseRefreshToken {

@ -26,14 +26,14 @@ type SocialBase struct {
info *social.OAuthInfo
log log.Logger
autoAssignOrgRole string
features featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
}
func newSocialBase(name string,
config *oauth2.Config,
info *social.OAuthInfo,
autoAssignOrgRole string,
features featuremgmt.FeatureManager,
features featuremgmt.FeatureToggles,
) *SocialBase {
logger := log.New("oauth." + name)

@ -37,7 +37,7 @@ type SocialService struct {
}
func ProvideService(cfg *setting.Cfg,
features *featuremgmt.FeatureManager,
features featuremgmt.FeatureToggles,
usageStats usagestats.Service,
bundleRegistry supportbundles.Service,
cache remotecache.CacheStorage,
@ -228,7 +228,7 @@ func (ss *SocialService) getUsageStats(ctx context.Context) (map[string]any, err
return m, nil
}
func createOAuthConnector(name string, info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features *featuremgmt.FeatureManager, cache remotecache.CacheStorage) (social.SocialConnector, error) {
func createOAuthConnector(name string, info *social.OAuthInfo, cfg *setting.Cfg, ssoSettings ssosettings.Service, features featuremgmt.FeatureToggles, cache remotecache.CacheStorage) (social.SocialConnector, error) {
switch name {
case social.AzureADProviderName:
return connectors.NewAzureADProvider(info, cfg, ssoSettings, features, cache), nil

@ -22,7 +22,7 @@ import (
func TestSocialService_ProvideService(t *testing.T) {
type testEnv struct {
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
}
testCases := []struct {
name string

@ -3,8 +3,8 @@ package config
import (
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/log"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
)
@ -44,7 +44,7 @@ type Cfg struct {
GrafanaAppURL string
GrafanaAppSubURL string
Features plugins.FeatureToggles
Features featuremgmt.FeatureToggles
AngularSupportEnabled bool
HideAngularDeprecation []string
@ -52,7 +52,7 @@ type Cfg struct {
func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string,
awsAllowedAuthProviders []string, awsAssumeRoleEnabled bool, awsExternalId string, azure *azsettings.AzureSettings, secureSocksDSProxy setting.SecureSocksDSProxySettings,
grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, appURL string, appSubURL string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool,
grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, appURL string, appSubURL string, tracing Tracing, features featuremgmt.FeatureToggles, angularSupportEnabled bool,
grafanaComURL string, disablePlugins []string, hideAngularDeprecation []string, forwardHostEnvVars []string) *Cfg {
return &Cfg{
log: log.New("plugin.cfg"),

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/auth"
"github.com/grafana/grafana/pkg/plugins/config"
@ -726,35 +727,35 @@ func TestService_GetConfigMap(t *testing.T) {
func TestService_GetConfigMap_featureToggles(t *testing.T) {
t.Run("Feature toggles list is deterministic", func(t *testing.T) {
tcs := []struct {
enabledFeatures []string
expectedConfig map[string]string
features featuremgmt.FeatureToggles
expectedConfig map[string]string
}{
{
enabledFeatures: nil,
expectedConfig: map[string]string{},
features: nil,
expectedConfig: map[string]string{},
},
{
enabledFeatures: []string{},
expectedConfig: map[string]string{},
features: featuremgmt.WithFeatures(),
expectedConfig: map[string]string{},
},
{
enabledFeatures: []string{"A", "B", "C"},
expectedConfig: map[string]string{"GF_INSTANCE_FEATURE_TOGGLES_ENABLE": "A,B,C"},
features: featuremgmt.WithFeatures("A", "B", "C"),
expectedConfig: map[string]string{"GF_INSTANCE_FEATURE_TOGGLES_ENABLE": "A,B,C"},
},
{
enabledFeatures: []string{"C", "B", "A"},
expectedConfig: map[string]string{"GF_INSTANCE_FEATURE_TOGGLES_ENABLE": "A,B,C"},
features: featuremgmt.WithFeatures("C", "B", "A"),
expectedConfig: map[string]string{"GF_INSTANCE_FEATURE_TOGGLES_ENABLE": "A,B,C"},
},
{
enabledFeatures: []string{"b", "a", "c", "d"},
expectedConfig: map[string]string{"GF_INSTANCE_FEATURE_TOGGLES_ENABLE": "a,b,c,d"},
features: featuremgmt.WithFeatures("b", "a", "c", "d"),
expectedConfig: map[string]string{"GF_INSTANCE_FEATURE_TOGGLES_ENABLE": "a,b,c,d"},
},
}
for _, tc := range tcs {
s := &Service{
cfg: &config.Cfg{
Features: fakes.NewFakeFeatureToggles(tc.enabledFeatures...),
Features: tc.features,
},
}
require.Equal(t, tc.expectedConfig, s.GetConfigMap(context.Background(), "", nil))

@ -149,11 +149,6 @@ func (fn ClientMiddlewareFunc) CreateClientMiddleware(next Client) Client {
return fn(next)
}
type FeatureToggles interface {
IsEnabledGlobally(flag string) bool
GetEnabled(ctx context.Context) map[string]bool
}
type SignatureCalculator interface {
Calculate(ctx context.Context, src PluginSource, plugin FoundPlugin) (Signature, error)
}

@ -574,26 +574,3 @@ func (p *FakeBackendPlugin) Kill() {
defer p.mutex.Unlock()
p.Running = false
}
type FakeFeatureToggles struct {
features map[string]bool
}
func NewFakeFeatureToggles(features ...string) *FakeFeatureToggles {
m := make(map[string]bool)
for _, f := range features {
m[f] = true
}
return &FakeFeatureToggles{
features: m,
}
}
func (f *FakeFeatureToggles) GetEnabled(_ context.Context) map[string]bool {
return f.features
}
func (f *FakeFeatureToggles) IsEnabledGlobally(feature string) bool {
return f.features[feature]
}

@ -26,10 +26,10 @@ var (
type Local struct {
log log.Logger
production bool
features plugins.FeatureToggles
features featuremgmt.FeatureToggles
}
func NewLocalFinder(devMode bool, features plugins.FeatureToggles) *Local {
func NewLocalFinder(devMode bool, features featuremgmt.FeatureToggles) *Local {
return &Local{
production: !devMode,
log: log.New("local.finder"),

@ -41,7 +41,7 @@ var SharedWithMeFolderPermission = accesscontrol.Permission{
}
func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegister, cache *localcache.CacheService,
accessControl accesscontrol.AccessControl, features *featuremgmt.FeatureManager) (*Service, error) {
accessControl accesscontrol.AccessControl, features featuremgmt.FeatureToggles) (*Service, error) {
service := ProvideOSSService(cfg, database.ProvideService(db), cache, features)
api.NewAccessControlAPI(routeRegister, accessControl, service, features).RegisterAPIEndpoints()
@ -62,7 +62,7 @@ func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegis
return service, nil
}
func ProvideOSSService(cfg *setting.Cfg, store store, cache *localcache.CacheService, features *featuremgmt.FeatureManager) *Service {
func ProvideOSSService(cfg *setting.Cfg, store store, cache *localcache.CacheService, features featuremgmt.FeatureToggles) *Service {
s := &Service{
cfg: cfg,
store: store,
@ -93,7 +93,7 @@ type Service struct {
cache *localcache.CacheService
registrations accesscontrol.RegistrationList
roles map[string]*accesscontrol.RoleDTO
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
}
func (s *Service) GetUsageStats(_ context.Context) map[string]any {

@ -15,7 +15,7 @@ import (
)
func NewAccessControlAPI(router routing.RouteRegister, accesscontrol ac.AccessControl, service ac.Service,
features *featuremgmt.FeatureManager) *AccessControlAPI {
features featuremgmt.FeatureToggles) *AccessControlAPI {
return &AccessControlAPI{
RouteRegister: router,
Service: service,
@ -28,7 +28,7 @@ type AccessControlAPI struct {
Service ac.Service
AccessControl ac.AccessControl
RouteRegister routing.RouteRegister
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
}
func (api *AccessControlAPI) RegisterAPIEndpoints() {

@ -19,7 +19,7 @@ var _ authn.HookClient = new(Session)
var _ authn.ContextAwareClient = new(Session)
func ProvideSession(cfg *setting.Cfg, sessionService auth.UserTokenService,
features *featuremgmt.FeatureManager) *Session {
features featuremgmt.FeatureToggles) *Session {
return &Session{
cfg: cfg,
features: features,
@ -30,7 +30,7 @@ func ProvideSession(cfg *setting.Cfg, sessionService auth.UserTokenService,
type Session struct {
cfg *setting.Cfg
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
sessionService auth.UserTokenService
log log.Logger
}

@ -64,7 +64,7 @@ func TestSession_Authenticate(t *testing.T) {
type fields struct {
sessionService auth.UserTokenService
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
}
type args struct {
r *authn.Request

@ -25,7 +25,7 @@ import (
"github.com/grafana/grafana/pkg/web"
)
func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, features *featuremgmt.FeatureManager, authnService authn.Service,
func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureToggles, authnService authn.Service,
) *ContextHandler {
return &ContextHandler{
Cfg: cfg,
@ -39,7 +39,7 @@ func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, features *featuremg
type ContextHandler struct {
Cfg *setting.Cfg
tracer tracing.Tracer
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
authnService authn.Service
}

@ -174,10 +174,14 @@ func (fm *FeatureManager) LookupFlag(name string) (FeatureFlag, bool) {
// ############# Test Functions #############
func WithFeatures(spec ...any) FeatureToggles {
return WithManager(spec...)
}
// WithFeatures is used to define feature toggles for testing.
// The arguments are a list of strings that are optionally followed by a boolean value for example:
// WithFeatures([]any{"my_feature", "other_feature"}) or WithFeatures([]any{"my_feature", true})
func WithFeatures(spec ...any) *FeatureManager {
func WithManager(spec ...any) *FeatureManager {
count := len(spec)
features := make(map[string]*FeatureFlag, count)
enabled := make(map[string]bool, count)

@ -9,7 +9,7 @@ import (
func TestFeatureManager(t *testing.T) {
t.Run("check testing stubs", func(t *testing.T) {
ft := WithFeatures("a", "b", "c")
ft := WithManager("a", "b", "c")
require.True(t, ft.IsEnabledGlobally("a"))
require.True(t, ft.IsEnabledGlobally("b"))
require.True(t, ft.IsEnabledGlobally("c"))
@ -18,7 +18,7 @@ func TestFeatureManager(t *testing.T) {
require.Equal(t, map[string]bool{"a": true, "b": true, "c": true}, ft.GetEnabled(context.Background()))
// Explicit values
ft = WithFeatures("a", true, "b", false)
ft = WithManager("a", true, "b", false)
require.True(t, ft.IsEnabledGlobally("a"))
require.False(t, ft.IsEnabledGlobally("b"))
require.Equal(t, map[string]bool{"a": true}, ft.GetEnabled(context.Background()))

@ -18,6 +18,10 @@ type FeatureToggles interface {
// Use of global feature flags should be limited and careful as they require
// a full server restart for a change to take place.
IsEnabledGlobally(flag string) bool
// Get the enabled flags -- this *may* also include disabled flags (with value false)
// but it is guaranteed to have the enabled ones listed
GetEnabled(ctx context.Context) map[string]bool
}
// FeatureFlagStage indicates the quality level

@ -8,7 +8,7 @@ import (
)
func TestFeatureUsageStats(t *testing.T) {
featureManagerWithAllFeatures := WithFeatures(
featureManagerWithAllFeatures := WithManager(
"database_metrics",
"live-config",
"UPPER_SNAKE_CASE",

@ -587,7 +587,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
testCases := []struct {
service *Service
featuresFlag *featuremgmt.FeatureManager
featuresFlag featuremgmt.FeatureToggles
prefix string
depth int
forceDelete bool

@ -33,7 +33,7 @@ type ServiceImpl struct {
pluginStore pluginstore.Store
pluginSettings pluginsettings.Service
starService star.Service
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
dashboardService dashboards.DashboardService
accesscontrolService ac.Service
kvStore kvstore.KVStore
@ -52,7 +52,7 @@ type NavigationAppConfig struct {
Icon string
}
func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStore pluginstore.Store, pluginSettings pluginsettings.Service, starService star.Service, features *featuremgmt.FeatureManager, dashboardService dashboards.DashboardService, accesscontrolService ac.Service, kvStore kvstore.KVStore, apiKeyService apikey.Service, license licensing.Licensing) navtree.Service {
func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStore pluginstore.Store, pluginSettings pluginsettings.Service, starService star.Service, features featuremgmt.FeatureToggles, dashboardService dashboards.DashboardService, accesscontrolService ac.Service, kvStore kvstore.KVStore, apiKeyService apikey.Service, license licensing.Licensing) navtree.Service {
service := &ServiceImpl{
cfg: cfg,
log: log.New("navtree service"),

@ -27,7 +27,7 @@ func NewCachingMiddleware(cachingService caching.CachingService) plugins.ClientM
// NewCachingMiddlewareWithFeatureManager creates a new plugins.ClientMiddleware that will
// attempt to read and write query results to the cache with a feature manager
func NewCachingMiddlewareWithFeatureManager(cachingService caching.CachingService, features *featuremgmt.FeatureManager) plugins.ClientMiddleware {
func NewCachingMiddlewareWithFeatureManager(cachingService caching.CachingService, features featuremgmt.FeatureToggles) plugins.ClientMiddleware {
log := log.New("caching_middleware")
if err := prometheus.Register(QueryCachingRequestHistogram); err != nil {
log.Error("Error registering prometheus collector 'QueryRequestHistogram'", "error", err)
@ -49,7 +49,7 @@ type CachingMiddleware struct {
next plugins.Client
caching caching.CachingService
log log.Logger
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
}
// QueryData receives a data request and attempts to access results already stored in the cache for that request.

@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func ProvideConfig(settingProvider setting.Provider, grafanaCfg *setting.Cfg, features *featuremgmt.FeatureManager) (*pCfg.Cfg, error) {
func ProvideConfig(settingProvider setting.Provider, grafanaCfg *setting.Cfg, features featuremgmt.FeatureToggles) (*pCfg.Cfg, error) {
plugins := settingProvider.Section("plugins")
allowedUnsigned := grafanaCfg.PluginsAllowUnsigned
if len(plugins.KeyValue("allow_loading_unsigned_plugins").Value()) > 0 {

@ -507,7 +507,7 @@ func TestLoader_Load_ExternalRegistration(t *testing.T) {
t.Run("Load a plugin with oauth client registration", func(t *testing.T) {
cfg := &config.Cfg{
Features: fakes.NewFakeFeatureToggles(featuremgmt.FlagExternalServiceAuth),
Features: featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAuth),
PluginsAllowUnsigned: []string{"grafana-test-datasource"},
}
pluginPaths := []string{filepath.Join(testDataDir(t), "oauth-external-registration")}
@ -608,7 +608,7 @@ func TestLoader_Load_ExternalRegistration(t *testing.T) {
t.Run("Load a plugin with service account registration", func(t *testing.T) {
cfg := &config.Cfg{
Features: fakes.NewFakeFeatureToggles(featuremgmt.FlagExternalServiceAuth),
Features: featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAuth),
PluginsAllowUnsigned: []string{"grafana-test-datasource"},
}
pluginPaths := []string{filepath.Join(testDataDir(t), "external-registration")}

@ -3,12 +3,13 @@ package pipeline
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/log"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
func TestSkipPlugins(t *testing.T) {

@ -27,7 +27,7 @@ type Api struct {
accessControl accesscontrol.AccessControl
cfg *setting.Cfg
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
log log.Logger
routeRegister routing.RouteRegister
}
@ -36,7 +36,7 @@ func ProvideApi(
pd publicdashboards.Service,
rr routing.RouteRegister,
ac accesscontrol.AccessControl,
features *featuremgmt.FeatureManager,
features featuremgmt.FeatureToggles,
md publicdashboards.Middleware,
cfg *setting.Cfg,
) *Api {
@ -297,7 +297,7 @@ func (api *Api) DeletePublicDashboard(c *contextmodel.ReqContext) response.Respo
}
// Copied from pkg/api/metrics.go
func toJsonStreamingResponse(ctx context.Context, features *featuremgmt.FeatureManager, qdr *backend.QueryDataResponse) response.Response {
func toJsonStreamingResponse(ctx context.Context, features featuremgmt.FeatureToggles, qdr *backend.QueryDataResponse) response.Response {
statusWhenError := http.StatusBadRequest
if features.IsEnabled(ctx, featuremgmt.FlagDatasourceQueryMultiStatus) {
statusWhenError = http.StatusMultiStatus

@ -155,6 +155,10 @@ func (f fakeFeatureToggles) IsEnabled(ctx context.Context, feature string) bool
return f.returnValue
}
func (f fakeFeatureToggles) GetEnabled(ctx context.Context) map[string]bool {
return map[string]bool{}
}
// Fake grpc secrets plugin impl
type fakeGRPCSecretsPlugin struct {
kv map[Key]string

@ -23,7 +23,7 @@ func SetupDisabledTestService(tb testing.TB, store secrets.Store) *SecretsServic
return setupTestService(tb, store, featuremgmt.WithFeatures(featuremgmt.FlagDisableEnvelopeEncryption))
}
func setupTestService(tb testing.TB, store secrets.Store, features *featuremgmt.FeatureManager) *SecretsService {
func setupTestService(tb testing.TB, store secrets.Store, features featuremgmt.FeatureToggles) *SecretsService {
tb.Helper()
defaultKey := "SdlklWklckeLS"
if len(setting.SecretKey) > 0 {

@ -26,7 +26,7 @@ import (
type ExtSvcAccountsService struct {
acSvc ac.Service
features *featuremgmt.FeatureManager
features featuremgmt.FeatureToggles
logger log.Logger
metrics *metrics
saSvc sa.Service

@ -4,6 +4,9 @@ import (
"context"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
@ -18,8 +21,6 @@ import (
sa "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
type TestEnv struct {

@ -195,7 +195,7 @@ func TestIntegration_DashboardPermissionFilter(t *testing.T) {
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}
for _, features := range []*featuremgmt.FeatureManager{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m))
for k := range m {
@ -394,7 +394,7 @@ func TestIntegration_DashboardPermissionFilter_WithSelfContainedPermissions(t *t
usr := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleViewer, AuthenticatedBy: login.ExtendedJWTModule, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.signedInUserPermissions)}}
for _, features := range []*featuremgmt.FeatureManager{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(), featuremgmt.WithFeatures(featuremgmt.FlagPermissionsFilterRemoveSubquery)} {
m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m))
for k := range m {
@ -545,7 +545,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) {
})
usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.permissions)}}
for _, features := range []*featuremgmt.FeatureManager{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m))
for k := range m {
@ -716,7 +716,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission
}),
},
}
for _, features := range []*featuremgmt.FeatureManager{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} {
m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m))
for k := range m {

@ -34,7 +34,7 @@ type SSOSettingsService struct {
}
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl,
routeRegister routing.RouteRegister, features *featuremgmt.FeatureManager,
routeRegister routing.RouteRegister, features featuremgmt.FeatureToggles,
secrets secrets.Service) *SSOSettingsService {
strategies := []ssosettings.FallbackStrategy{
strategies.NewOAuthStrategy(cfg),

@ -118,22 +118,8 @@ func GetMockService(version string, rt RoundTripper) *Service {
version: version,
fakeRoundTripper: rt,
},
features: &fakeFeatureToggles{
flags: map[string]bool{
featuremgmt.FlagInfluxqlStreamingParser: false,
},
},
}
}
type fakeFeatureToggles struct {
flags map[string]bool
}
func (f *fakeFeatureToggles) IsEnabledGlobally(flag string) bool {
return f.flags[flag]
}
func (f *fakeFeatureToggles) IsEnabled(ctx context.Context, flag string) bool {
return f.flags[flag]
// featuremgmt.FlagInfluxqlStreamingParser: false
features: featuremgmt.WithFeatures(),
}
}

Loading…
Cancel
Save