FeatureFlags: Use interface rather than manager (#80000)

This commit is contained in:
Ryan McKinley
2024-01-09 10:38:06 -08:00
committed by GitHub
parent e550829dae
commit 1caaa56de0
48 changed files with 125 additions and 143 deletions

View File

@ -600,7 +600,7 @@ func (hs *HTTPServer) GetAnnotationTags(c *contextmodel.ReqContext) response.Res
// where <type> is the type of annotation with id <id>. // 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 // 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). // 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("") prefix := accesscontrol.ScopeAnnotationsProvider.GetResourceScope("")
return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) { return prefix, accesscontrol.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, initialScope string) ([]string, error) {
scopeParts := strings.Split(initialScope, ":") scopeParts := strings.Split(initialScope, ":")

View File

@ -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)}} 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 { if features == nil {
features = featuremgmt.WithFeatures() features = featuremgmt.WithFeatures()
} }

View File

@ -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. // 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. // 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 // 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 { if jsonData == nil {
return nil return nil
} }

View File

@ -25,7 +25,7 @@ func (hs *HTTPServer) GetFeatureToggles(ctx *contextmodel.ReqContext) response.R
dtos := make([]featuremgmt.FeatureToggleDTO, 0) dtos := make([]featuremgmt.FeatureToggleDTO, 0)
// loop through features an add features that should be visible to dtos // 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) { if isFeatureHidden(ft, cfg.HiddenToggles) {
continue continue
} }
@ -67,7 +67,7 @@ func (hs *HTTPServer) UpdateFeatureToggle(ctx *contextmodel.ReqContext) response
for _, t := range cmd.FeatureToggles { for _, t := range cmd.FeatureToggles {
// make sure flag exists, and only continue if flag is writeable // 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) 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) payload.FeatureToggles[t.Name] = strconv.FormatBool(t.Enabled)
} else { } else {
@ -82,13 +82,13 @@ func (hs *HTTPServer) UpdateFeatureToggle(ctx *contextmodel.ReqContext) response
return response.Respond(http.StatusBadRequest, "Failed to perform webhook request") 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") return response.Respond(http.StatusOK, "feature toggles updated successfully")
} }
func (hs *HTTPServer) GetFeatureMgmtState(ctx *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) GetFeatureMgmtState(ctx *contextmodel.ReqContext) response.Response {
fmState := hs.Features.GetState() fmState := hs.featureManager.GetState()
return response.Respond(http.StatusOK, fmState) return response.Respond(http.StatusOK, fmState)
} }

View File

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

View File

@ -95,7 +95,7 @@ func BenchmarkFolderListAndSearch(b *testing.B) {
desc string desc string
url string url string
expectedLen int expectedLen int
features *featuremgmt.FeatureManager features featuremgmt.FeatureToggles
}{ }{
{ {
desc: "impl=default nested_folders=on get root folders", 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() b.Helper()
m := web.New() m := web.New()

View File

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

View File

@ -32,7 +32,7 @@ import (
"github.com/grafana/grafana/pkg/web" "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() t.Helper()
db.InitTestDB(t) db.InitTestDB(t)
// nolint:staticcheck // nolint:staticcheck

View File

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

View File

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

View File

@ -72,10 +72,10 @@ type keySetJWKS struct {
jose.JSONWebKeySet 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) config := createOAuthConfig(info, cfg, social.AzureADProviderName)
provider := &SocialAzureAD{ provider := &SocialAzureAD{
SocialBase: newSocialBase(social.AzureADProviderName, config, info, cfg.AutoAssignOrgRole, *features), SocialBase: newSocialBase(social.AzureADProviderName, config, info, cfg.AutoAssignOrgRole, features),
cache: cache, cache: cache,
allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]), allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]),
forceUseGraphAPI: MustBool(info.Extra[forceUseGraphAPIKey], false), forceUseGraphAPI: MustBool(info.Extra[forceUseGraphAPIKey], false),

View File

@ -45,10 +45,10 @@ type SocialGenericOAuth struct {
teamIds []string 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) config := createOAuthConfig(info, cfg, social.GenericOAuthProviderName)
provider := &SocialGenericOAuth{ provider := &SocialGenericOAuth{
SocialBase: newSocialBase(social.GenericOAuthProviderName, config, info, cfg.AutoAssignOrgRole, *features), SocialBase: newSocialBase(social.GenericOAuthProviderName, config, info, cfg.AutoAssignOrgRole, features),
teamsUrl: info.TeamsUrl, teamsUrl: info.TeamsUrl,
emailAttributeName: info.EmailAttributeName, emailAttributeName: info.EmailAttributeName,
emailAttributePath: info.EmailAttributePath, emailAttributePath: info.EmailAttributePath,

View File

@ -53,13 +53,13 @@ var (
"User is not a member of one of the required organizations. Please contact identity provider administrator.")) "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]) teamIdsSplitted := util.SplitString(info.Extra[teamIdsKey])
teamIds := mustInts(teamIdsSplitted) teamIds := mustInts(teamIdsSplitted)
config := createOAuthConfig(info, cfg, social.GitHubProviderName) config := createOAuthConfig(info, cfg, social.GitHubProviderName)
provider := &SocialGithub{ provider := &SocialGithub{
SocialBase: newSocialBase(social.GitHubProviderName, config, info, cfg.AutoAssignOrgRole, *features), SocialBase: newSocialBase(social.GitHubProviderName, config, info, cfg.AutoAssignOrgRole, features),
teamIds: teamIds, teamIds: teamIds,
allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]), allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]),
} }

View File

@ -52,10 +52,10 @@ type userData struct {
IsGrafanaAdmin *bool `json:"-"` 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) config := createOAuthConfig(info, cfg, social.GitlabProviderName)
provider := &SocialGitlab{ 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) { if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) {

View File

@ -38,10 +38,10 @@ type googleUserData struct {
rawJSON []byte `json:"-"` 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) config := createOAuthConfig(info, cfg, social.GoogleProviderName)
provider := &SocialGoogle{ 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) { if strings.HasPrefix(info.ApiUrl, legacyAPIURL) {

View File

@ -33,7 +33,7 @@ type OrgRecord struct {
Login string `json:"login"` 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 // Override necessary settings
info.AuthUrl = cfg.GrafanaComURL + "/oauth2/authorize" info.AuthUrl = cfg.GrafanaComURL + "/oauth2/authorize"
info.TokenUrl = cfg.GrafanaComURL + "/api/oauth2/token" 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) config := createOAuthConfig(info, cfg, social.GrafanaComProviderName)
provider := &SocialGrafanaCom{ provider := &SocialGrafanaCom{
SocialBase: newSocialBase(social.GrafanaComProviderName, config, info, cfg.AutoAssignOrgRole, *features), SocialBase: newSocialBase(social.GrafanaComProviderName, config, info, cfg.AutoAssignOrgRole, features),
url: cfg.GrafanaComURL, url: cfg.GrafanaComURL,
allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]), allowedOrganizations: util.SplitString(info.Extra[allowedOrganizationsKey]),
} }

View File

@ -44,10 +44,10 @@ type OktaClaims struct {
Name string `json:"name"` 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) config := createOAuthConfig(info, cfg, social.OktaProviderName)
provider := &SocialOkta{ provider := &SocialOkta{
SocialBase: newSocialBase(social.OktaProviderName, config, info, cfg.AutoAssignOrgRole, *features), SocialBase: newSocialBase(social.OktaProviderName, config, info, cfg.AutoAssignOrgRole, features),
} }
if info.UseRefreshToken { if info.UseRefreshToken {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -574,26 +574,3 @@ func (p *FakeBackendPlugin) Kill() {
defer p.mutex.Unlock() defer p.mutex.Unlock()
p.Running = false 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]
}

View File

@ -26,10 +26,10 @@ var (
type Local struct { type Local struct {
log log.Logger log log.Logger
production bool 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{ return &Local{
production: !devMode, production: !devMode,
log: log.New("local.finder"), log: log.New("local.finder"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -174,10 +174,14 @@ func (fm *FeatureManager) LookupFlag(name string) (FeatureFlag, bool) {
// ############# Test Functions ############# // ############# Test Functions #############
func WithFeatures(spec ...any) FeatureToggles {
return WithManager(spec...)
}
// WithFeatures is used to define feature toggles for testing. // 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: // 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}) // 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) count := len(spec)
features := make(map[string]*FeatureFlag, count) features := make(map[string]*FeatureFlag, count)
enabled := make(map[string]bool, count) enabled := make(map[string]bool, count)

View File

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

View File

@ -18,6 +18,10 @@ type FeatureToggles interface {
// Use of global feature flags should be limited and careful as they require // Use of global feature flags should be limited and careful as they require
// a full server restart for a change to take place. // a full server restart for a change to take place.
IsEnabledGlobally(flag string) bool 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 // FeatureFlagStage indicates the quality level

View File

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

View File

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

View File

@ -33,7 +33,7 @@ type ServiceImpl struct {
pluginStore pluginstore.Store pluginStore pluginstore.Store
pluginSettings pluginsettings.Service pluginSettings pluginsettings.Service
starService star.Service starService star.Service
features *featuremgmt.FeatureManager features featuremgmt.FeatureToggles
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
accesscontrolService ac.Service accesscontrolService ac.Service
kvStore kvstore.KVStore kvStore kvstore.KVStore
@ -52,7 +52,7 @@ type NavigationAppConfig struct {
Icon string 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{ service := &ServiceImpl{
cfg: cfg, cfg: cfg,
log: log.New("navtree service"), log: log.New("navtree service"),

View File

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

View File

@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana/pkg/setting" "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") plugins := settingProvider.Section("plugins")
allowedUnsigned := grafanaCfg.PluginsAllowUnsigned allowedUnsigned := grafanaCfg.PluginsAllowUnsigned
if len(plugins.KeyValue("allow_loading_unsigned_plugins").Value()) > 0 { if len(plugins.KeyValue("allow_loading_unsigned_plugins").Value()) > 0 {

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ func SetupDisabledTestService(tb testing.TB, store secrets.Store) *SecretsServic
return setupTestService(tb, store, featuremgmt.WithFeatures(featuremgmt.FlagDisableEnvelopeEncryption)) 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() tb.Helper()
defaultKey := "SdlklWklckeLS" defaultKey := "SdlklWklckeLS"
if len(setting.SecretKey) > 0 { if len(setting.SecretKey) > 0 {

View File

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

View File

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

View File

@ -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)}} 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()) m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m)) keys := make([]string, 0, len(m))
for k := range 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)}} 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()) m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m)) keys := make([]string, 0, len(m))
for k := range 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)}} 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()) m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m)) keys := make([]string, 0, len(m))
for k := range 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()) m := features.GetEnabled(context.Background())
keys := make([]string, 0, len(m)) keys := make([]string, 0, len(m))
for k := range m { for k := range m {

View File

@ -34,7 +34,7 @@ type SSOSettingsService struct {
} }
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, 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 { secrets secrets.Service) *SSOSettingsService {
strategies := []ssosettings.FallbackStrategy{ strategies := []ssosettings.FallbackStrategy{
strategies.NewOAuthStrategy(cfg), strategies.NewOAuthStrategy(cfg),

View File

@ -118,22 +118,8 @@ func GetMockService(version string, rt RoundTripper) *Service {
version: version, version: version,
fakeRoundTripper: rt, fakeRoundTripper: rt,
}, },
features: &fakeFeatureToggles{
flags: map[string]bool{
featuremgmt.FlagInfluxqlStreamingParser: false,
},
},
}
}
type fakeFeatureToggles struct { // featuremgmt.FlagInfluxqlStreamingParser: false
flags map[string]bool features: featuremgmt.WithFeatures(),
} }
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]
} }