diff --git a/pkg/api/api.go b/pkg/api/api.go index bd14803eb13..550b51de3d9 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -108,13 +108,13 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/admin/orgs/edit/:id", authorizeInOrg(ac.UseGlobalOrg, ac.OrgsAccessEvaluator), hs.Index) r.Get("/admin/stats", authorize(ac.EvalPermission(ac.ActionServerStatsRead)), hs.Index) r.Get("/admin/authentication/ldap", authorize(ac.EvalPermission(ac.ActionLDAPStatusRead)), hs.Index) - if hs.Features.IsEnabled(featuremgmt.FlagStorage) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagStorage) { r.Get("/admin/storage", reqSignedIn, hs.Index) r.Get("/admin/storage/*", reqSignedIn, hs.Index) } // feature toggle admin page - if hs.Features.IsEnabled(featuremgmt.FlagFeatureToggleAdminPage) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagFeatureToggleAdminPage) { r.Get("/admin/featuretoggles", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.Index) } @@ -156,11 +156,11 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/dashboards/*", reqSignedIn, hs.Index) r.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, hs.Index) - if hs.Features.IsEnabled(featuremgmt.FlagDashboardEmbed) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagDashboardEmbed) { r.Get("/d-embed", reqSignedIn, middleware.AddAllowEmbeddingHeader(), hs.Index) } - if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) { // list public dashboards r.Get("/public-dashboards/list", reqSignedIn, hs.Index) @@ -216,7 +216,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/swagger-ui", swaggerUI) r.Get("/openapi3", openapi3) - if hs.Features.IsEnabled(featuremgmt.FlagClientTokenRotation) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagClientTokenRotation) { r.Post("/api/user/auth-tokens/rotate", routing.Wrap(hs.RotateUserAuthToken)) r.Get("/user/auth-tokens/rotate", routing.Wrap(hs.RotateUserAuthTokenRedirect)) } @@ -277,12 +277,12 @@ func (hs *HTTPServer) registerRoutes() { orgRoute.Get("/quotas", authorize(ac.EvalPermission(ac.ActionOrgsQuotasRead)), routing.Wrap(hs.GetCurrentOrgQuotas)) }) - if hs.Features.IsEnabled(featuremgmt.FlagStorage) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagStorage) { // Will eventually be replaced with the 'object' route apiRoute.Group("/storage", hs.StorageService.RegisterHTTPRoutes) } - if hs.Features.IsEnabled(featuremgmt.FlagPanelTitleSearch) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch) { apiRoute.Group("/search-v2", hs.SearchV2HTTPService.RegisterHTTPRoutes) } @@ -392,7 +392,7 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Any("/plugin-proxy/:pluginId/*", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), hs.ProxyPluginRequest) apiRoute.Any("/plugin-proxy/:pluginId", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), hs.ProxyPluginRequest) - if hs.Cfg.PluginAdminEnabled && (hs.Features.IsEnabled(featuremgmt.FlagManagedPluginsInstall) || !hs.Cfg.PluginAdminExternalManageEnabled) { + if hs.Cfg.PluginAdminEnabled && (hs.Features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall) || !hs.Cfg.PluginAdminExternalManageEnabled) { apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { pluginRoute.Post("/:pluginId/install", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin)) pluginRoute.Post("/:pluginId/uninstall", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.UninstallPlugin)) @@ -405,7 +405,7 @@ func (hs *HTTPServer) registerRoutes() { pluginRoute.Get("/:pluginId/metrics", reqOrgAdmin, routing.Wrap(hs.CollectPluginMetrics)) }) - if hs.Features.IsEnabled(featuremgmt.FlagFeatureToggleAdminPage) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagFeatureToggleAdminPage) { apiRoute.Group("/featuremgmt", func(featuremgmtRoute routing.RouteRegister) { featuremgmtRoute.Get("/state", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.GetFeatureMgmtState) featuremgmtRoute.Get("/", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.GetFeatureToggles) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 5e186555ec7..b8174db2476 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -228,7 +228,7 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer { features = featuremgmt.WithFeatures() } // nolint:staticcheck - cfg := setting.NewCfgWithFeatures(features.IsEnabled) + cfg := setting.NewCfgWithFeatures(features.IsEnabledGlobally) return &HTTPServer{ Cfg: cfg, diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 783d8a24b63..3567411c464 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -94,7 +94,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response // If public dashboards is enabled and we have a public dashboard, update meta // values - if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) { publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.SignedInUser.GetOrgID(), dash.UID) if err != nil && !errors.Is(err, publicdashboardModels.ErrPublicDashboardNotFound) { return response.Error(http.StatusInternalServerError, "Error while retrieving public dashboards", err) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index c0d12237812..26724f07469 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -15,6 +15,7 @@ import ( "golang.org/x/exp/slices" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -356,7 +357,7 @@ func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setti } // Prevent adding a data source team header with a name that matches the auth proxy header name - if features.IsEnabled(featuremgmt.FlagTeamHttpHeaders) { + if features.IsEnabled(ctx, featuremgmt.FlagTeamHttpHeaders) { err := validateTeamHTTPHeaderJSON(jsonData) if err != nil { return err diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 3d930a77913..c953c3dc9aa 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -43,7 +43,7 @@ const REDACTED = "redacted" func (hs *HTTPServer) GetFolders(c *contextmodel.ReqContext) response.Response { var folders []*folder.Folder var err error - if hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) { + if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNestedFolders) { folders, err = hs.folderService.GetChildren(c.Req.Context(), &folder.GetChildrenQuery{ OrgID: c.SignedInUser.GetOrgID(), Limit: c.QueryInt64("limit"), @@ -190,7 +190,7 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int } isNested := folder.ParentUID != "" - if !isNested || !hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) { + if !isNested || !hs.Features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ {BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()}, {BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.String()}, @@ -212,7 +212,7 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) MoveFolder(c *contextmodel.ReqContext) response.Response { - if hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) { + if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNestedFolders) { cmd := folder.MoveFolderCommand{} if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) @@ -390,7 +390,7 @@ func (hs *HTTPServer) newToFolderDto(c *contextmodel.ReqContext, f *folder.Folde return dtos.Folder{}, err } - if !hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) { + if !hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNestedFolders) { return folderDTO, nil } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 48e048a2a27..af901f458ef 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -66,7 +66,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro continue } - if panel.ID == "datagrid" && !hs.Features.IsEnabled(featuremgmt.FlagEnableDatagridEditing) { + if panel.ID == "datagrid" && !hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagEnableDatagridEditing) { continue } diff --git a/pkg/api/frontendsettings_test.go b/pkg/api/frontendsettings_test.go index a59ee967b94..6fbbdc3cfa9 100644 --- a/pkg/api/frontendsettings_test.go +++ b/pkg/api/frontendsettings_test.go @@ -35,7 +35,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt. t.Helper() db.InitTestDB(t) // nolint:staticcheck - cfg.IsFeatureToggleEnabled = features.IsEnabled + cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally { oldVersion := setting.BuildVersion diff --git a/pkg/api/index.go b/pkg/api/index.go index b105887e272..5f9f334d57a 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -38,7 +38,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV return nil, err } - if hs.Features.IsEnabled(featuremgmt.FlagIndividualCookiePreferences) { + if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagIndividualCookiePreferences) { if !prefs.Cookies("analytics") { settings.GoogleAnalytics4Id = "" settings.GoogleAnalyticsId = "" @@ -166,7 +166,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV hs.HooksService.RunIndexDataHooks(&data, c) - data.NavTree.ApplyAdminIA(hs.Features.IsEnabled(featuremgmt.FlagNavAdminSubsections)) + data.NavTree.ApplyAdminIA(hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNavAdminSubsections)) data.NavTree.Sort() return &data, nil @@ -244,7 +244,7 @@ func (hs *HTTPServer) getThemeForIndexData(themePrefId string, themeURLParam str if pref.IsValidThemeID(themePrefId) { theme := pref.GetThemeByID(themePrefId) - if !theme.IsExtra || hs.Features.IsEnabled(featuremgmt.FlagExtraThemes) { + if !theme.IsExtra || hs.Features.IsEnabledGlobally(featuremgmt.FlagExtraThemes) { return theme } } diff --git a/pkg/api/login.go b/pkg/api/login.go index 81728e3f9b6..4edc1f81400 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -91,7 +91,7 @@ func (hs *HTTPServer) CookieOptionsFromCfg() cookies.CookieOptions { } func (hs *HTTPServer) LoginView(c *contextmodel.ReqContext) { - if hs.Features.IsEnabled(featuremgmt.FlagClientTokenRotation) { + if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagClientTokenRotation) { if errors.Is(c.LookupTokenErr, authn.ErrTokenNeedsRotation) { c.Redirect(hs.Cfg.AppSubURL + "/") return @@ -334,7 +334,7 @@ func (hs *HTTPServer) RedirectResponseWithError(c *contextmodel.ReqContext, err func (hs *HTTPServer) redirectURLWithErrorCookie(c *contextmodel.ReqContext, err error) string { setCookie := true - if hs.Features.IsEnabled(featuremgmt.FlagIndividualCookiePreferences) { + if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagIndividualCookiePreferences) { var userID int64 if c.SignedInUser != nil && !c.SignedInUser.IsNil() { var errID error diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index ea201d52146..f6acadf1358 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -63,7 +63,7 @@ func (hs *HTTPServer) QueryMetricsV2(c *contextmodel.ReqContext) response.Respon func (hs *HTTPServer) toJsonStreamingResponse(ctx context.Context, qdr *backend.QueryDataResponse) response.Response { statusWhenError := http.StatusBadRequest - if hs.Features.IsEnabled(featuremgmt.FlagDatasourceQueryMultiStatus) { + if hs.Features.IsEnabled(ctx, featuremgmt.FlagDatasourceQueryMultiStatus) { statusWhenError = http.StatusMultiStatus } diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go index 011751188da..9c2f0f94ff9 100644 --- a/pkg/api/playlist.go +++ b/pkg/api/playlist.go @@ -26,7 +26,7 @@ import ( func (hs *HTTPServer) registerPlaylistAPI(apiRoute routing.RouteRegister) { // Register the actual handlers apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) { - if hs.Features.IsEnabled(featuremgmt.FlagKubernetesPlaylists) { + if hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesPlaylists) { // Use k8s client to implement legacy API handler := newPlaylistK8sHandler(hs) playlistRoute.Get("/", handler.searchPlaylists) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 87c449d13c1..f1b2e8e2469 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -270,7 +270,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) { } } - if proxy.features.IsEnabled(featuremgmt.FlagIdForwarding) && auth.IsIDForwardingEnabledForDataSource(proxy.ds) { + if proxy.features.IsEnabled(req.Context(), featuremgmt.FlagIdForwarding) && auth.IsIDForwardingEnabledForDataSource(proxy.ds) { proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser) } } diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index ba1bda14c54..b206591a656 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -7,6 +7,8 @@ import ( "net/http" "net/url" + "go.opentelemetry.io/otel/attribute" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -17,7 +19,6 @@ import ( "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/proxyutil" "github.com/grafana/grafana/pkg/web" - "go.opentelemetry.io/otel/attribute" ) type PluginProxy struct { @@ -161,7 +162,7 @@ func (proxy PluginProxy) director(req *http.Request) { proxyutil.ApplyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser) - if proxy.features.IsEnabled(featuremgmt.FlagIdForwarding) { + if proxy.features.IsEnabled(req.Context(), featuremgmt.FlagIdForwarding) { proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser) } diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index 86668d27579..58919d1b72c 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -61,7 +61,7 @@ type DataPipeline []Node func (dp *DataPipeline) execute(c context.Context, now time.Time, s *Service) (mathexp.Vars, error) { vars := make(mathexp.Vars) - groupByDSFlag := s.features.IsEnabled(featuremgmt.FlagSseGroupByDatasource) + groupByDSFlag := s.features.IsEnabled(c, featuremgmt.FlagSseGroupByDatasource) // Execute datasource nodes first, and grouped by datasource. if groupByDSFlag { dsNodes := []*DSNode{} @@ -227,7 +227,7 @@ func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) { case TypeCMDNode: node, err = buildCMDNode(rn, s.features) case TypeMLNode: - if s.features.IsEnabled(featuremgmt.FlagMlExpressions) { + if s.features.IsEnabledGlobally(featuremgmt.FlagMlExpressions) { node, err = s.buildMLNode(dp, rn, req) if err != nil { err = fmt.Errorf("fail to parse expression with refID %v: %w", rn.RefID, err) diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index bb98c995f3b..87579f94262 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -388,7 +388,7 @@ func convertDataFramesToResults(ctx context.Context, frames data.Frames, datasou } var dt data.FrameType - dt, useDataplane, _ := shouldUseDataplane(frames, logger, s.features.IsEnabled(featuremgmt.FlagDisableSSEDataplane)) + dt, useDataplane, _ := shouldUseDataplane(frames, logger, s.features.IsEnabled(ctx, featuremgmt.FlagDisableSSEDataplane)) if useDataplane { logger.Debug("Handling SSE data source query through dataplane", "datatype", dt) result, err := handleDataplaneFrames(ctx, s.tracer, dt, frames) diff --git a/pkg/expr/threshold.go b/pkg/expr/threshold.go index f49eea59b5f..2fa6e750243 100644 --- a/pkg/expr/threshold.go +++ b/pkg/expr/threshold.go @@ -81,7 +81,7 @@ func UnmarshalThresholdCommand(rn *rawNode, features featuremgmt.FeatureToggles) if err != nil { return nil, fmt.Errorf("invalid condition: %w", err) } - if firstCondition.UnloadEvaluator != nil && features.IsEnabled(featuremgmt.FlagRecoveryThreshold) { + if firstCondition.UnloadEvaluator != nil && features.IsEnabledGlobally(featuremgmt.FlagRecoveryThreshold) { unloading, err := NewThresholdCommand(rn.RefID, referenceVar, firstCondition.UnloadEvaluator.Type, firstCondition.UnloadEvaluator.Params) unloading.Invert = true if err != nil { diff --git a/pkg/login/social/google_oauth.go b/pkg/login/social/google_oauth.go index 1303b12e5c7..ce76a6863cb 100644 --- a/pkg/login/social/google_oauth.go +++ b/pkg/login/social/google_oauth.go @@ -134,7 +134,7 @@ func (s *SocialGoogle) extractFromAPI(ctx context.Context, client *http.Client) } func (s *SocialGoogle) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string { - if s.features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) && s.useRefreshToken { + if s.features.IsEnabledGlobally(featuremgmt.FlagAccessTokenExpirationCheck) && s.useRefreshToken { opts = append(opts, oauth2.AccessTypeOffline, oauth2.ApprovalForce) } return s.SocialBase.AuthCodeURL(state, opts...) diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index ca04c32d6f7..108842fe281 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -206,7 +206,7 @@ func ProvideService(cfg *setting.Cfg, forceUseGraphAPI: sec.Key("force_use_graph_api").MustBool(false), skipOrgRoleSync: cfg.AzureADSkipOrgRoleSync, } - if info.UseRefreshToken && features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) { + if info.UseRefreshToken && features.IsEnabledGlobally(featuremgmt.FlagAccessTokenExpirationCheck) { appendUniqueScope(&config, OfflineAccessScope) } } @@ -219,7 +219,7 @@ func ProvideService(cfg *setting.Cfg, allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), skipOrgRoleSync: cfg.OktaSkipOrgRoleSync, } - if info.UseRefreshToken && features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) { + if info.UseRefreshToken && features.IsEnabledGlobally(featuremgmt.FlagAccessTokenExpirationCheck) { appendUniqueScope(&config, OfflineAccessScope) } } diff --git a/pkg/middleware/loggermw/logger.go b/pkg/middleware/loggermw/logger.go index 89254828442..a4bfe1003bc 100644 --- a/pkg/middleware/loggermw/logger.go +++ b/pkg/middleware/loggermw/logger.go @@ -64,7 +64,7 @@ func (l *loggerImpl) Middleware() web.Middleware { // put the start time on context so we can measure it later. r = r.WithContext(log.InitstartTime(r.Context(), time.Now())) - if l.flags.IsEnabled(featuremgmt.FlagUnifiedRequestLog) { + if l.flags.IsEnabled(r.Context(), featuremgmt.FlagUnifiedRequestLog) { r = r.WithContext(errutil.SetUnifiedLogging(r.Context())) } @@ -128,7 +128,7 @@ func (l *loggerImpl) prepareLogParams(c *contextmodel.ReqContext, duration time. logParams = append(logParams, "handler", handler) } - if l.flags.IsEnabled(featuremgmt.FlagRequestInstrumentationStatusSource) { + if l.flags.IsEnabled(r.Context(), featuremgmt.FlagRequestInstrumentationStatusSource) { rmd := requestmeta.GetRequestMetaData(c.Req.Context()) logParams = append(logParams, "status_source", rmd.StatusSource) } diff --git a/pkg/middleware/request_metrics.go b/pkg/middleware/request_metrics.go index ad25e314be0..ad614581a0b 100644 --- a/pkg/middleware/request_metrics.go +++ b/pkg/middleware/request_metrics.go @@ -37,7 +37,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR histogramLabels := []string{"handler", "status_code", "method"} - if features.IsEnabled(featuremgmt.FlagRequestInstrumentationStatusSource) { + if features.IsEnabledGlobally(featuremgmt.FlagRequestInstrumentationStatusSource) { histogramLabels = append(histogramLabels, "status_source") } @@ -45,7 +45,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR histogramLabels = append(histogramLabels, "grafana_team") } - if features.IsEnabled(featuremgmt.FlagHttpSLOLevels) { + if features.IsEnabledGlobally(featuremgmt.FlagHttpSLOLevels) { histogramLabels = append(histogramLabels, "slo_group") } @@ -56,7 +56,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR Buckets: defBuckets, } - if features.IsEnabled(featuremgmt.FlagEnableNativeHTTPHistogram) { + if features.IsEnabledGlobally(featuremgmt.FlagEnableNativeHTTPHistogram) { // the recommended default value from the prom_client // https://github.com/prometheus/client_golang/blob/main/prometheus/histogram.go#L411 // Giving this variable an value means the client will expose the histograms as an @@ -95,7 +95,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR handler = "notfound" } else { // log requests where we could not identify handler so we can register them. - if features.IsEnabled(featuremgmt.FlagLogRequestsInstrumentedAsUnknown) { + if features.IsEnabled(r.Context(), featuremgmt.FlagLogRequestsInstrumentedAsUnknown) { log.Warn("request instrumented as unknown", "path", r.URL.Path, "status_code", status) } } @@ -104,7 +104,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR labelValues := []string{handler, code, r.Method} rmd := requestmeta.GetRequestMetaData(r.Context()) - if features.IsEnabled(featuremgmt.FlagRequestInstrumentationStatusSource) { + if features.IsEnabled(r.Context(), featuremgmt.FlagRequestInstrumentationStatusSource) { labelValues = append(labelValues, string(rmd.StatusSource)) } @@ -112,7 +112,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR labelValues = append(labelValues, rmd.Team) } - if features.IsEnabled(featuremgmt.FlagHttpSLOLevels) { + if features.IsEnabled(r.Context(), featuremgmt.FlagHttpSLOLevels) { labelValues = append(labelValues, string(rmd.SLOGroup)) } diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index 2ea6bf6dccb..16ff4d5fd13 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -155,7 +155,7 @@ func (fn ClientMiddlewareFunc) CreateClientMiddleware(next Client) Client { } type FeatureToggles interface { - IsEnabled(flag string) bool + IsEnabledGlobally(flag string) bool GetEnabled(ctx context.Context) map[string]bool } diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index d33a61d27dc..82ed833fb05 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -594,6 +594,6 @@ func (f *FakeFeatureToggles) GetEnabled(_ context.Context) map[string]bool { return f.features } -func (f *FakeFeatureToggles) IsEnabled(feature string) bool { +func (f *FakeFeatureToggles) IsEnabledGlobally(feature string) bool { return f.features[feature] } diff --git a/pkg/plugins/manager/loader/assetpath/assetpath.go b/pkg/plugins/manager/loader/assetpath/assetpath.go index 90ec28f87d9..cc610f8d340 100644 --- a/pkg/plugins/manager/loader/assetpath/assetpath.go +++ b/pkg/plugins/manager/loader/assetpath/assetpath.go @@ -59,7 +59,7 @@ func (s *Service) Base(n PluginInfo) (string, error) { func (s *Service) Module(n PluginInfo) (string, error) { if n.class == plugins.ClassCore { if s.cfg.Features != nil && - s.cfg.Features.IsEnabled(featuremgmt.FlagExternalCorePlugins) && + s.cfg.Features.IsEnabledGlobally(featuremgmt.FlagExternalCorePlugins) && filepath.Base(n.dir) == "dist" { // The core plugin has been built externally, use the module from the dist folder } else { diff --git a/pkg/registry/apis/example/register.go b/pkg/registry/apis/example/register.go index 0632b164aec..ea2ee6b2367 100644 --- a/pkg/registry/apis/example/register.go +++ b/pkg/registry/apis/example/register.go @@ -37,7 +37,7 @@ type TestingAPIBuilder struct { } func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder { - if !features.IsEnabled(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { + if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { return nil // skip registration unless opting into experimental apis } builder := &TestingAPIBuilder{ diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 19c95959331..67eeee8003c 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -43,7 +43,7 @@ func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegis return nil, err } - if features.IsEnabled(featuremgmt.FlagSplitScopes) { + if features.IsEnabledGlobally(featuremgmt.FlagSplitScopes) { // Migrating scopes that haven't been split yet to have kind, attribute and identifier in the DB // This will be removed once we've: // 1) removed the feature toggle and @@ -213,9 +213,9 @@ func permissionCacheKey(user identity.Requester) string { // DeclarePluginRoles allow the caller to declare, to the service, plugin roles and their assignments // to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin" -func (s *Service) DeclarePluginRoles(_ context.Context, ID, name string, regs []plugins.RoleRegistration) error { +func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs []plugins.RoleRegistration) error { // Protect behind feature toggle - if !s.features.IsEnabled(featuremgmt.FlagAccessControlOnCall) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) { return nil } @@ -397,7 +397,7 @@ func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchO } func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { - if !(s.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) || s.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts)) { + if !(s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAuth) || s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts)) { s.log.Debug("Registering an external service role is behind a feature flag, enable it to use this feature.") return nil } @@ -410,7 +410,7 @@ func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol } func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error { - if !(s.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) || s.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts)) { + if !(s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAuth) || s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts)) { s.log.Debug("Deleting an external service role is behind a feature flag, enable it to use this feature.") return nil } diff --git a/pkg/services/accesscontrol/api/api.go b/pkg/services/accesscontrol/api/api.go index 5b6bd3d4f0f..241d367c88a 100644 --- a/pkg/services/accesscontrol/api/api.go +++ b/pkg/services/accesscontrol/api/api.go @@ -37,7 +37,7 @@ func (api *AccessControlAPI) RegisterAPIEndpoints() { api.RouteRegister.Group("/api/access-control", func(rr routing.RouteRegister) { rr.Get("/user/actions", middleware.ReqSignedIn, routing.Wrap(api.getUserActions)) rr.Get("/user/permissions", middleware.ReqSignedIn, routing.Wrap(api.getUserPermissions)) - if api.features.IsEnabled(featuremgmt.FlagAccessControlOnCall) { + if api.features.IsEnabledGlobally(featuremgmt.FlagAccessControlOnCall) { userIDScope := ac.Scope("users", "id", ac.Parameter(":userID")) rr.Get("/users/permissions/search", authorize(ac.EvalPermission(ac.ActionUsersPermissionsRead)), routing.Wrap(api.searchUsersPermissions)) rr.Get("/user/:userID/permissions/search", authorize(ac.EvalPermission(ac.ActionUsersPermissionsRead, userIDScope)), routing.Wrap(api.searchUserPermissions)) diff --git a/pkg/services/accesscontrol/resourcepermissions/store.go b/pkg/services/accesscontrol/resourcepermissions/store.go index 00fb2635649..3d62b0d6ad9 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store.go +++ b/pkg/services/accesscontrol/resourcepermissions/store.go @@ -655,7 +655,7 @@ func (s *store) createPermissions(sess *db.Session, roleID int64, resource, reso p.RoleID = roleID p.Created = time.Now() p.Updated = time.Now() - if s.features.IsEnabled(featuremgmt.FlagSplitScopes) { + if s.features.IsEnabledGlobally(featuremgmt.FlagSplitScopes) { p.Kind, p.Attribute, p.Identifier = p.SplitScope() } permissions = append(permissions, p) diff --git a/pkg/services/auth/idimpl/service.go b/pkg/services/auth/idimpl/service.go index 92074df5c33..ca7ff43896f 100644 --- a/pkg/services/auth/idimpl/service.go +++ b/pkg/services/auth/idimpl/service.go @@ -32,7 +32,7 @@ func ProvideService( ) *Service { s := &Service{cfg: cfg, logger: log.New("id-service"), signer: signer, cache: cache, metrics: newMetrics(reg)} - if features.IsEnabled(featuremgmt.FlagIdForwarding) { + if features.IsEnabledGlobally(featuremgmt.FlagIdForwarding) { authnService.RegisterPostAuthHook(s.hook, 140) } diff --git a/pkg/services/auth/idimpl/signer.go b/pkg/services/auth/idimpl/signer.go index 4cd8a090503..5bb004deb11 100644 --- a/pkg/services/auth/idimpl/signer.go +++ b/pkg/services/auth/idimpl/signer.go @@ -28,7 +28,7 @@ type LocalSigner struct { } func (s *LocalSigner) SignIDToken(ctx context.Context, claims *auth.IDClaims) (string, error) { - if !s.features.IsEnabled(featuremgmt.FlagIdForwarding) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagIdForwarding) { return "", nil } diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 4c50c97e9ff..ce7d6f13455 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -132,7 +132,7 @@ func ProvideService( s.RegisterClient(clients.ProvideJWT(jwtService, cfg)) } - if s.cfg.ExtendedJWTAuthEnabled && features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if s.cfg.ExtendedJWTAuthEnabled && features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) { s.RegisterClient(clients.ProvideExtendedJWT(userService, cfg, signingKeysService, oauthServer)) } @@ -159,7 +159,7 @@ func ProvideService( s.RegisterPostAuthHook(orgUserSyncService.SyncOrgRolesHook, 30) s.RegisterPostAuthHook(userSyncService.SyncLastSeenHook, 120) - if features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) { + if features.IsEnabledGlobally(featuremgmt.FlagAccessTokenExpirationCheck) { s.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService).SyncOauthTokenHook, 60) } diff --git a/pkg/services/authn/clients/session.go b/pkg/services/authn/clients/session.go index c97a87d27f1..dc8219c0240 100644 --- a/pkg/services/authn/clients/session.go +++ b/pkg/services/authn/clients/session.go @@ -55,7 +55,7 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id return nil, err } - if s.features.IsEnabled(featuremgmt.FlagClientTokenRotation) { + if s.features.IsEnabled(ctx, featuremgmt.FlagClientTokenRotation) { if token.NeedsRotation(time.Duration(s.cfg.TokenRotationIntervalMinutes) * time.Minute) { return nil, authn.ErrTokenNeedsRotation.Errorf("token needs to be rotated") } @@ -88,7 +88,7 @@ func (s *Session) Priority() uint { } func (s *Session) Hook(ctx context.Context, identity *authn.Identity, r *authn.Request) error { - if identity.SessionToken == nil || s.features.IsEnabled(featuremgmt.FlagClientTokenRotation) { + if identity.SessionToken == nil || s.features.IsEnabled(ctx, featuremgmt.FlagClientTokenRotation) { return nil } diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index ab49137b0e7..ee90cef3648 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -6,6 +6,9 @@ import ( "errors" "net/http" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -18,8 +21,6 @@ import ( "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" ) func ProvideService(cfg *setting.Cfg, tracer tracing.Tracer, features *featuremgmt.FeatureManager, authnService authn.Service, @@ -140,7 +141,7 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler { func (h *ContextHandler) deleteInvalidCookieEndOfRequestFunc(reqContext *contextmodel.ReqContext) web.BeforeFunc { return func(w web.ResponseWriter) { - if h.features.IsEnabled(featuremgmt.FlagClientTokenRotation) { + if h.features.IsEnabled(reqContext.Req.Context(), featuremgmt.FlagClientTokenRotation) { return } diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index fea61c5cd09..2b52dae49fa 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -66,7 +66,7 @@ func ProvideDashboardStore(sqlStore db.DB, cfg *setting.Cfg, features featuremgm } func (d *dashboardStore) emitEntityEvent() bool { - return d.features != nil && d.features.IsEnabled(featuremgmt.FlagPanelTitleSearch) + return d.features != nil && d.features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch) } func (d *dashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dashboard *dashboards.Dashboard, overwrite bool) (bool, error) { @@ -1010,7 +1010,12 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F } if len(query.FolderUIDs) > 0 { - filters = append(filters, searchstore.FolderUIDFilter{Dialect: d.store.GetDialect(), OrgID: orgID, UIDs: query.FolderUIDs, NestedFoldersEnabled: d.features.IsEnabled(featuremgmt.FlagNestedFolders)}) + filters = append(filters, searchstore.FolderUIDFilter{ + Dialect: d.store.GetDialect(), + OrgID: orgID, + UIDs: query.FolderUIDs, + NestedFoldersEnabled: d.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders), + }) } var res []dashboards.DashboardSearchProjection diff --git a/pkg/services/datasources/service/datasource.go b/pkg/services/datasources/service/datasource.go index fe193937529..82b51b9c678 100644 --- a/pkg/services/datasources/service/datasource.go +++ b/pkg/services/datasources/service/datasource.go @@ -198,7 +198,7 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *datasources.AddDataSou var err error cmd.EncryptedSecureJsonData = make(map[string][]byte) - if !s.features.IsEnabled(featuremgmt.FlagDisableSecretsCompatibility) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagDisableSecretsCompatibility) { cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope()) if err != nil { return err @@ -689,7 +689,7 @@ func (s *Service) fillWithSecureJSONData(ctx context.Context, cmd *datasources.U } cmd.EncryptedSecureJsonData = make(map[string][]byte) - if !s.features.IsEnabled(featuremgmt.FlagDisableSecretsCompatibility) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagDisableSecretsCompatibility) { cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope()) if err != nil { return err diff --git a/pkg/services/extsvcauth/oauthserver/oasimpl/service.go b/pkg/services/extsvcauth/oauthserver/oasimpl/service.go index cfe694676dd..24f6b4366e1 100644 --- a/pkg/services/extsvcauth/oauthserver/oasimpl/service.go +++ b/pkg/services/extsvcauth/oauthserver/oasimpl/service.go @@ -65,7 +65,7 @@ type OAuth2ServiceImpl struct { func ProvideService(router routing.RouteRegister, bus bus.Bus, db db.DB, cfg *setting.Cfg, extSvcAccSvc serviceaccounts.ExtSvcAccountsService, accessControl ac.AccessControl, acSvc ac.Service, userSvc user.Service, teamSvc team.Service, keySvc signingkeys.Service, fmgmt *featuremgmt.FeatureManager) (*OAuth2ServiceImpl, error) { - if !fmgmt.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if !fmgmt.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) { return nil, nil } config := &fosite.Config{ diff --git a/pkg/services/extsvcauth/registry/service.go b/pkg/services/extsvcauth/registry/service.go index 6a718494336..204e93ec3c9 100644 --- a/pkg/services/extsvcauth/registry/service.go +++ b/pkg/services/extsvcauth/registry/service.go @@ -51,14 +51,14 @@ func (r *Registry) RemoveExternalService(ctx context.Context, name string) error switch provider { case extsvcauth.ServiceAccounts: - if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) { + if !r.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { r.logger.Debug("Skipping External Service removal, flag disabled", "service", name, "flag", featuremgmt.FlagExternalServiceAccounts) return nil } r.logger.Debug("Routing External Service removal to the External Service Account service", "service", name) return r.saReg.RemoveExternalService(ctx, name) case extsvcauth.OAuth2Server: - if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if !r.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAuth) { r.logger.Debug("Skipping External Service removal, flag disabled", "service", name, "flag", featuremgmt.FlagExternalServiceAccounts) return nil } @@ -80,14 +80,14 @@ func (r *Registry) SaveExternalService(ctx context.Context, cmd *extsvcauth.Exte switch cmd.AuthProvider { case extsvcauth.ServiceAccounts: - if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) { + if !r.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { r.logger.Warn("Skipping External Service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAccounts) return nil, nil } r.logger.Debug("Routing the External Service registration to the External Service Account service", "service", cmd.Name) return r.saReg.SaveExternalService(ctx, cmd) case extsvcauth.OAuth2Server: - if !r.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if !r.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAuth) { r.logger.Warn("Skipping External Service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAuth) return nil, nil } diff --git a/pkg/services/featuremgmt/manager.go b/pkg/services/featuremgmt/manager.go index 95ab6180580..b2c962dc1c2 100644 --- a/pkg/services/featuremgmt/manager.go +++ b/pkg/services/featuremgmt/manager.go @@ -126,7 +126,12 @@ func (fm *FeatureManager) readFile() error { } // IsEnabled checks if a feature is enabled -func (fm *FeatureManager) IsEnabled(flag string) bool { +func (fm *FeatureManager) IsEnabled(ctx context.Context, flag string) bool { + return fm.enabled[flag] +} + +// IsEnabledGlobally checks if a feature is for all tenants +func (fm *FeatureManager) IsEnabledGlobally(flag string) bool { return fm.enabled[flag] } diff --git a/pkg/services/featuremgmt/manager_test.go b/pkg/services/featuremgmt/manager_test.go index 68c31daf9a9..83bdd73995d 100644 --- a/pkg/services/featuremgmt/manager_test.go +++ b/pkg/services/featuremgmt/manager_test.go @@ -10,17 +10,17 @@ import ( func TestFeatureManager(t *testing.T) { t.Run("check testing stubs", func(t *testing.T) { ft := WithFeatures("a", "b", "c") - require.True(t, ft.IsEnabled("a")) - require.True(t, ft.IsEnabled("b")) - require.True(t, ft.IsEnabled("c")) - require.False(t, ft.IsEnabled("d")) + require.True(t, ft.IsEnabledGlobally("a")) + require.True(t, ft.IsEnabledGlobally("b")) + require.True(t, ft.IsEnabledGlobally("c")) + require.False(t, ft.IsEnabledGlobally("d")) require.Equal(t, map[string]bool{"a": true, "b": true, "c": true}, ft.GetEnabled(context.Background())) // Explicit values ft = WithFeatures("a", true, "b", false) - require.True(t, ft.IsEnabled("a")) - require.False(t, ft.IsEnabled("b")) + require.True(t, ft.IsEnabledGlobally("a")) + require.False(t, ft.IsEnabledGlobally("b")) require.Equal(t, map[string]bool{"a": true}, ft.GetEnabled(context.Background())) }) @@ -37,9 +37,9 @@ func TestFeatureManager(t *testing.T) { Name: "b", Expression: "true", }) - require.False(t, ft.IsEnabled("a")) - require.True(t, ft.IsEnabled("b")) - require.False(t, ft.IsEnabled("c")) // uknown flag + require.False(t, ft.IsEnabledGlobally("a")) + require.True(t, ft.IsEnabledGlobally("b")) + require.False(t, ft.IsEnabledGlobally("c")) // uknown flag // Try changing "requires license" ft.registerFlags(FeatureFlag{ @@ -49,9 +49,9 @@ func TestFeatureManager(t *testing.T) { Name: "b", RequiresLicense: true, // expression is still "true" }) - require.False(t, ft.IsEnabled("a")) - require.False(t, ft.IsEnabled("b")) - require.False(t, ft.IsEnabled("c")) + require.False(t, ft.IsEnabledGlobally("a")) + require.False(t, ft.IsEnabledGlobally("b")) + require.False(t, ft.IsEnabledGlobally("c")) }) t.Run("check description and docs configs", func(t *testing.T) { diff --git a/pkg/services/featuremgmt/models.go b/pkg/services/featuremgmt/models.go index 54bc36766a5..ca5985f1eb7 100644 --- a/pkg/services/featuremgmt/models.go +++ b/pkg/services/featuremgmt/models.go @@ -2,11 +2,21 @@ package featuremgmt import ( "bytes" + "context" "encoding/json" ) type FeatureToggles interface { - IsEnabled(flag string) bool + // Check if a feature is enabled for a given context. + // The settings may be per user, tenant, or globally set in the cloud + IsEnabled(ctx context.Context, flag string) bool + + // Check if a flag is configured globally. For now, this is the same + // as the function above, however it will move to only checking flags that + // are configured by the operator and shared across all tenants. + // 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 } // FeatureFlagStage indicates the quality level diff --git a/pkg/services/featuremgmt/service.go b/pkg/services/featuremgmt/service.go index 03c1e09b2fd..88e93fda851 100644 --- a/pkg/services/featuremgmt/service.go +++ b/pkg/services/featuremgmt/service.go @@ -74,7 +74,7 @@ func ProvideManagerService(cfg *setting.Cfg, licensing licensing.Licensing) (*Fe // Minimum approach to avoid circular dependency // nolint:staticcheck - cfg.IsFeatureToggleEnabled = mgmt.IsEnabled + cfg.IsFeatureToggleEnabled = mgmt.IsEnabledGlobally return mgmt, nil } diff --git a/pkg/services/featuremgmt/service_test.go b/pkg/services/featuremgmt/service_test.go index 660dbda8136..b69753fa362 100644 --- a/pkg/services/featuremgmt/service_test.go +++ b/pkg/services/featuremgmt/service_test.go @@ -43,8 +43,8 @@ func TestFeatureService(t *testing.T) { require.NotNil(t, mgmt) // Enterprise features do not fall though automatically - require.False(t, mgmt.IsEnabled("a.yes.default")) - require.False(t, mgmt.IsEnabled("a.yes")) // licensed, but not enabled + require.False(t, mgmt.IsEnabledGlobally("a.yes.default")) + require.False(t, mgmt.IsEnabledGlobally("a.yes")) // licensed, but not enabled } var ( diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 0609c70ed1c..d7244b19016 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -156,7 +156,7 @@ func (s *Service) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder. return nil, dashboards.ErrFolderAccessDenied } - if !s.features.IsEnabled(featuremgmt.FlagNestedFolders) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { return dashFolder, nil } @@ -328,7 +328,7 @@ func (s *Service) deduplicateAvailableFolders(ctx context.Context, folders []*fo } func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) { - if !s.features.IsEnabled(featuremgmt.FlagNestedFolders) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { return nil, nil } return s.store.GetParents(ctx, q) @@ -360,7 +360,7 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) ( dashFolder := dashboards.NewDashboardFolder(cmd.Title) dashFolder.OrgID = cmd.OrgID - if s.features.IsEnabled(featuremgmt.FlagNestedFolders) && cmd.ParentUID != "" { + if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) && cmd.ParentUID != "" { // Check that the user is allowed to create a subfolder in this folder evaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.ParentUID)) hasAccess, evalErr := s.accessControl.Evaluate(ctx, cmd.SignedInUser, evaluator) @@ -801,7 +801,7 @@ func (s *Service) GetDescendantCounts(ctx context.Context, cmd *folder.GetDescen result := []string{*cmd.UID} countsMap := make(folder.DescendantCounts, len(s.registry)+1) - if s.features.IsEnabled(featuremgmt.FlagNestedFolders) { + if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { subfolders, err := s.getNestedFolders(ctx, cmd.OrgID, *cmd.UID) if err != nil { logger.Error("failed to get subfolders", "error", err) diff --git a/pkg/services/grafana-apiserver/config.go b/pkg/services/grafana-apiserver/config.go index fbd6c91edfc..0dc71673d18 100644 --- a/pkg/services/grafana-apiserver/config.go +++ b/pkg/services/grafana-apiserver/config.go @@ -46,7 +46,7 @@ func newConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles) *config { host := fmt.Sprintf("%s:%d", ip, port) return &config{ - enabled: features.IsEnabled(featuremgmt.FlagGrafanaAPIServer), + enabled: features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServer), devMode: cfg.Env == setting.Dev, dataPath: filepath.Join(cfg.DataPath, "grafana-apiserver"), ip: ip, diff --git a/pkg/services/grpcserver/service.go b/pkg/services/grpcserver/service.go index 024451b22c1..b820662f5d1 100644 --- a/pkg/services/grpcserver/service.go +++ b/pkg/services/grpcserver/service.go @@ -45,7 +45,7 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, authe s := &gPRCServerService{ cfg: cfg, logger: log.New("grpc-server"), - enabled: features.IsEnabled(featuremgmt.FlagGrpcServer), + enabled: features.IsEnabledGlobally(featuremgmt.FlagGrpcServer), } // Register the metric here instead of an init() function so that we do diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index de6c7237d4f..c44e081738b 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -21,7 +21,7 @@ func (l *LibraryElementService) registerAPIEndpoints() { l.RouteRegister.Group("/api/library-elements", func(entities routing.RouteRegister) { uidScope := ScopeLibraryPanelsProvider.GetResourceScopeUID(ac.Parameter(":uid")) - if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) { + if l.features.IsEnabledGlobally(featuremgmt.FlagLibraryPanelRBAC) { entities.Post("/", authorize(ac.EvalPermission(ActionLibraryPanelsCreate)), routing.Wrap(l.createHandler)) entities.Delete("/:uid", authorize(ac.EvalPermission(ActionLibraryPanelsDelete, uidScope)), routing.Wrap(l.deleteHandler)) entities.Get("/", authorize(ac.EvalPermission(ActionLibraryPanelsRead)), routing.Wrap(l.getAllHandler)) @@ -176,7 +176,7 @@ func (l *LibraryElementService) getAllHandler(c *contextmodel.ReqContext) respon return toLibraryElementError(err, "Failed to get library elements") } - if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) { + if l.features.IsEnabled(c.Req.Context(), featuremgmt.FlagLibraryPanelRBAC) { filteredPanels, err := l.filterLibraryPanelsByPermission(c, elementsResult.Elements) if err != nil { return toLibraryElementError(err, "Failed to evaluate permissions") @@ -278,7 +278,7 @@ func (l *LibraryElementService) getByNameHandler(c *contextmodel.ReqContext) res return toLibraryElementError(err, "Failed to get library element") } - if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) { + if l.features.IsEnabled(c.Req.Context(), featuremgmt.FlagLibraryPanelRBAC) { filteredElements, err := l.filterLibraryPanelsByPermission(c, elements) if err != nil { return toLibraryElementError(err, err.Error()) diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index ef6c44464f1..a3a7bdd56de 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -166,7 +166,7 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn } err = l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { - if l.features.IsEnabled(featuremgmt.FlagLibraryPanelRBAC) { + if l.features.IsEnabled(c, featuremgmt.FlagLibraryPanelRBAC) { allowed, err := l.AccessControl.Evaluate(c, signedInUser, ac.EvalPermission(ActionLibraryPanelsCreate, dashboards.ScopeFoldersProvider.GetResourceScopeUID(*cmd.FolderUID))) if !allowed { return fmt.Errorf("insufficient permissions for creating library panel in folder with UID %s", *cmd.FolderUID) diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index d02c9f8c142..5817c1d9579 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -12,6 +12,7 @@ import ( func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink, error) { var configNodes []*navtree.NavLink + ctx := c.Req.Context() hasAccess := ac.HasAccess(s.accessControl, c) hasGlobalAccess := ac.HasGlobalAccess(s.accessControl, s.accesscontrolService, c) orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead) @@ -55,7 +56,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink }) } - disabled, err := s.apiKeyService.IsDisabled(c.Req.Context(), c.SignedInUser.GetOrgID()) + disabled, err := s.apiKeyService.IsDisabled(ctx, c.SignedInUser.GetOrgID()) if err != nil { return nil, err } @@ -101,7 +102,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink }) } - if s.features.IsEnabled(featuremgmt.FlagFeatureToggleAdminPage) && hasAccess(ac.EvalPermission(ac.ActionFeatureManagementRead)) { + if s.features.IsEnabled(ctx, featuremgmt.FlagFeatureToggleAdminPage) && hasAccess(ac.EvalPermission(ac.ActionFeatureManagementRead)) { configNodes = append(configNodes, &navtree.NavLink{ Text: "Feature Toggles", SubTitle: "View and edit feature toggles", @@ -111,7 +112,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink }) } - if s.features.IsEnabled(featuremgmt.FlagCorrelations) && hasAccess(correlations.ConfigurationPageAccess) { + if s.features.IsEnabled(ctx, featuremgmt.FlagCorrelations) && hasAccess(correlations.ConfigurationPageAccess) { configNodes = append(configNodes, &navtree.NavLink{ Text: "Correlations", Icon: "gf-glue", @@ -121,7 +122,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink }) } - if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(featuremgmt.FlagStorage) { + if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(ctx, featuremgmt.FlagStorage) { storage := &navtree.NavLink{ Text: "Storage", Id: "storage", diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 111ea757cea..4a7092f1111 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -238,7 +238,7 @@ func (s *ServiceImpl) addPluginToSection(c *contextmodel.ReqContext, treeRoot *n func (s *ServiceImpl) hasAccessToInclude(c *contextmodel.ReqContext, pluginID string) func(include *plugins.Includes) bool { hasAccess := ac.HasAccess(s.accessControl, c) return func(include *plugins.Includes) bool { - useRBAC := s.features.IsEnabled(featuremgmt.FlagAccessControlOnCall) && include.RequiresRBACAction() + useRBAC := s.features.IsEnabledGlobally(featuremgmt.FlagAccessControlOnCall) && include.RequiresRBACAction() if useRBAC && !hasAccess(ac.EvalPermission(include.Action)) { s.log.Debug("plugin include is covered by RBAC, user doesn't have access", "plugin", pluginID, @@ -267,7 +267,7 @@ func (s *ServiceImpl) readNavigationSettings() { "k6-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightAlertsAndIncidents + 1, Text: "Performance testing", Icon: "k6"}, } - if s.features.IsEnabled(featuremgmt.FlagNavAdminSubsections) && s.features.IsEnabled(featuremgmt.FlagCostManagementUi) { + if s.features.IsEnabledGlobally(featuremgmt.FlagNavAdminSubsections) && s.features.IsEnabledGlobally(featuremgmt.FlagCostManagementUi) { // if cost management is enabled we want to nest adaptive metrics and log volume explorer under that plugin // in the admin section s.navigationAppConfig["grafana-adaptive-metrics-app"] = NavigationAppConfig{SectionID: navtree.NavIDCfg} diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 496e429565a..5ae0795e2bd 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -358,7 +358,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt Icon: "library-panel", }) - if s.features.IsEnabled(featuremgmt.FlagPublicDashboards) { + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPublicDashboards) { dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "Public dashboards", Id: "dashboards/public", @@ -368,7 +368,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt } } - if s.features.IsEnabled(featuremgmt.FlagScenes) { + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagScenes) { dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "Scenes", Id: "scenes", diff --git a/pkg/services/ngalert/api/api_testing.go b/pkg/services/ngalert/api/api_testing.go index 66d26b2f575..9bb05852f0f 100644 --- a/pkg/services/ngalert/api/api_testing.go +++ b/pkg/services/ngalert/api/api_testing.go @@ -184,7 +184,7 @@ func (srv TestingApiSrv) RouteEvalQueries(c *contextmodel.ReqContext, cmd apimod } func (srv TestingApiSrv) BacktestAlertRule(c *contextmodel.ReqContext, cmd apimodels.BacktestConfig) response.Response { - if !srv.featureManager.IsEnabled(featuremgmt.FlagAlertingBacktesting) { + if !srv.featureManager.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingBacktesting) { return ErrResp(http.StatusNotFound, nil, "Backgtesting API is not enabled") } diff --git a/pkg/services/ngalert/migration/store/testing.go b/pkg/services/ngalert/migration/store/testing.go index 2a6434a5e1b..ae8612fb685 100644 --- a/pkg/services/ngalert/migration/store/testing.go +++ b/pkg/services/ngalert/migration/store/testing.go @@ -41,6 +41,7 @@ func NewTestMigrationStore(t *testing.T, sqlStore *sqlstore.SQLStore, cfg *setti cfg.UnifiedAlerting.BaseInterval = time.Second * 10 } features := featuremgmt.WithFeatures() + cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally alertingStore := store.DBstore{ SQLStore: sqlStore, Cfg: cfg.UnifiedAlerting, diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index f3a62a324b6..cf9aac166fb 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -246,9 +246,9 @@ func (ng *AlertNG) init() error { Images: ng.ImageService, Clock: clk, Historian: history, - DoNotSaveNormalState: ng.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoNormalState), + DoNotSaveNormalState: ng.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingNoNormalState), MaxStateSaveConcurrency: ng.Cfg.UnifiedAlerting.MaxStateSaveConcurrency, - ApplyNoDataAndErrorToAllStates: ng.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoDataErrorExecution), + ApplyNoDataAndErrorToAllStates: ng.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingNoDataErrorExecution), Tracer: ng.tracer, Log: log.New("ngalert.state.manager"), } @@ -483,7 +483,7 @@ func applyStateHistoryFeatureToggles(cfg *setting.UnifiedAlertingStateHistorySet // If all toggles are enabled, we listen to the state history config as written. // If any of them are disabled, we ignore the configured backend and treat the toggles as an override. // If multiple toggles are disabled, we go with the most "restrictive" one. - if !ft.IsEnabled(featuremgmt.FlagAlertStateHistoryLokiSecondary) { + if !ft.IsEnabledGlobally(featuremgmt.FlagAlertStateHistoryLokiSecondary) { // If we cannot even treat Loki as a secondary, we must use annotations only. if backend == historian.BackendTypeMultiple || backend == historian.BackendTypeLoki { logger.Info("Forcing Annotation backend due to state history feature toggles") @@ -493,7 +493,7 @@ func applyStateHistoryFeatureToggles(cfg *setting.UnifiedAlertingStateHistorySet } return } - if !ft.IsEnabled(featuremgmt.FlagAlertStateHistoryLokiPrimary) { + if !ft.IsEnabledGlobally(featuremgmt.FlagAlertStateHistoryLokiPrimary) { // If we're using multiple backends, Loki must be the secondary. if backend == historian.BackendTypeMultiple { logger.Info("Coercing Loki to a secondary backend due to state history feature toggles") @@ -509,7 +509,7 @@ func applyStateHistoryFeatureToggles(cfg *setting.UnifiedAlertingStateHistorySet } return } - if !ft.IsEnabled(featuremgmt.FlagAlertStateHistoryLokiOnly) { + if !ft.IsEnabledGlobally(featuremgmt.FlagAlertStateHistoryLokiOnly) { // If we're not allowed to use Loki only, make it the primary but keep the annotation writes. if backend == historian.BackendTypeLoki { logger.Info("Forcing dual writes to Loki and Annotations due to state history feature toggles") diff --git a/pkg/services/ngalert/store/instance_database.go b/pkg/services/ngalert/store/instance_database.go index 5e6a3274724..37571ae85fa 100644 --- a/pkg/services/ngalert/store/instance_database.go +++ b/pkg/services/ngalert/store/instance_database.go @@ -30,7 +30,7 @@ func (st DBstore) ListAlertInstances(ctx context.Context, cmd *models.ListAlertI if cmd.RuleUID != "" { addToQuery(` AND rule_uid = ?`, cmd.RuleUID) } - if st.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoNormalState) { + if st.FeatureToggles.IsEnabled(ctx, featuremgmt.FlagAlertingNoNormalState) { s.WriteString(fmt.Sprintf(" AND NOT (current_state = '%s' AND current_reason = '')", models.InstanceStateNormal)) } if err := sess.SQL(s.String(), params...).Find(&alertInstances); err != nil { diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 358a8a2e91d..068f00b5e9b 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -44,8 +44,6 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG, tb.Helper() cfg := setting.NewCfg() - // nolint:staticcheck - cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ BaseInterval: setting.SchedulerBaseInterval, } diff --git a/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go b/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go index a04a33b4cf3..a2b326df0f0 100644 --- a/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go +++ b/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go @@ -220,7 +220,7 @@ func (d *Dynamic) setDetectorsFromCache(ctx context.Context) error { // IsDisabled returns true if FlagPluginsDynamicAngularDetectionPatterns is not enabled. func (d *Dynamic) IsDisabled() bool { - return !d.features.IsEnabled(featuremgmt.FlagPluginsDynamicAngularDetectionPatterns) + return !d.features.IsEnabledGlobally(featuremgmt.FlagPluginsDynamicAngularDetectionPatterns) } // randomSkew returns a random time.Duration between 0 and maxSkew. diff --git a/pkg/services/pluginsintegration/angularinspector/angularinspector.go b/pkg/services/pluginsintegration/angularinspector/angularinspector.go index 3ac8734df5d..9eb8b20ab39 100644 --- a/pkg/services/pluginsintegration/angularinspector/angularinspector.go +++ b/pkg/services/pluginsintegration/angularinspector/angularinspector.go @@ -16,7 +16,7 @@ func ProvideService(cfg *config.Cfg, dynamic *angulardetectorsprovider.Dynamic) var detectorsProvider angulardetector.DetectorsProvider var err error static := angularinspector.NewDefaultStaticDetectorsProvider() - if cfg.Features != nil && cfg.Features.IsEnabled(featuremgmt.FlagPluginsDynamicAngularDetectionPatterns) { + if cfg.Features != nil && cfg.Features.IsEnabledGlobally(featuremgmt.FlagPluginsDynamicAngularDetectionPatterns) { detectorsProvider = angulardetector.SequenceDetectorsProvider{dynamic, static} } else { detectorsProvider = static diff --git a/pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go index ffbf17bcac8..3bc00043462 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go @@ -7,12 +7,13 @@ import ( "github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/caching" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/prometheus/client_golang/prometheus" ) // needed to mock the function for testing @@ -93,7 +94,7 @@ func (m *CachingMiddleware) QueryData(ctx context.Context, req *backend.QueryDat // Update the query cache with the result for this metrics request if err == nil && cr.UpdateCacheFn != nil { // If AWS async caching is not enabled, use the old code path - if m.features == nil || !m.features.IsEnabled(featuremgmt.FlagAwsAsyncQueryCaching) { + if m.features == nil || !m.features.IsEnabled(ctx, featuremgmt.FlagAwsAsyncQueryCaching) { cr.UpdateCacheFn(ctx, resp) } else { // time how long shouldCacheQuery takes diff --git a/pkg/services/pluginsintegration/clientmiddleware/logger_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/logger_middleware.go index d5dfda520c0..5dc5eea6094 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/logger_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/logger_middleware.go @@ -50,7 +50,7 @@ func (m *LoggerMiddleware) logRequest(ctx context.Context, fn func(ctx context.C if err != nil { logParams = append(logParams, "error", err) } - if m.features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { + if m.features.IsEnabled(ctx, featuremgmt.FlagPluginsInstrumentationStatusSource) { logParams = append(logParams, "statusSource", pluginrequestmeta.StatusSourceFromContext(ctx)) } @@ -82,7 +82,7 @@ func (m *LoggerMiddleware) QueryData(ctx context.Context, req *backend.QueryData for refID, dr := range resp.Responses { if dr.Error != nil { logParams := []any{"refID", refID, "status", int(dr.Status), "error", dr.Error} - if m.features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { + if m.features.IsEnabled(ctx, featuremgmt.FlagPluginsInstrumentationStatusSource) { logParams = append(logParams, "statusSource", pluginrequestmeta.StatusSourceFromPluginErrorSource(dr.ErrorSource)) } ctxLogger.Error("Partial data response error", logParams...) diff --git a/pkg/services/pluginsintegration/clientmiddleware/metrics_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/metrics_middleware.go index afd0efc9d3c..329117f5c87 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/metrics_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/metrics_middleware.go @@ -33,7 +33,7 @@ type MetricsMiddleware struct { func newMetricsMiddleware(promRegisterer prometheus.Registerer, pluginRegistry registry.Service, features featuremgmt.FeatureToggles) *MetricsMiddleware { var additionalLabels []string - if features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { + if features.IsEnabledGlobally(featuremgmt.FlagPluginsInstrumentationStatusSource) { additionalLabels = []string{"status_source"} } pluginRequestCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ @@ -122,7 +122,7 @@ func (m *MetricsMiddleware) instrumentPluginRequest(ctx context.Context, pluginC pluginRequestDurationLabels := []string{pluginCtx.PluginID, endpoint, target} pluginRequestCounterLabels := []string{pluginCtx.PluginID, endpoint, status.String(), target} pluginRequestDurationSecondsLabels := []string{"grafana-backend", pluginCtx.PluginID, endpoint, status.String(), target} - if m.features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { + if m.features.IsEnabled(ctx, featuremgmt.FlagPluginsInstrumentationStatusSource) { statusSource := pluginrequestmeta.StatusSourceFromContext(ctx) pluginRequestDurationLabels = append(pluginRequestDurationLabels, string(statusSource)) pluginRequestCounterLabels = append(pluginRequestCounterLabels, string(statusSource)) diff --git a/pkg/services/pluginsintegration/pipeline/steps.go b/pkg/services/pluginsintegration/pipeline/steps.go index e8996d136a9..0ac59f81597 100644 --- a/pkg/services/pluginsintegration/pipeline/steps.go +++ b/pkg/services/pluginsintegration/pipeline/steps.go @@ -175,7 +175,7 @@ func NewAsExternalStep(cfg *config.Cfg) *AsExternal { // Filter will filter out any plugins that are marked to be disabled. func (c *AsExternal) Filter(cl plugins.Class, bundles []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) { - if c.cfg.Features == nil || !c.cfg.Features.IsEnabled(featuremgmt.FlagExternalCorePlugins) { + if c.cfg.Features == nil || !c.cfg.Features.IsEnabledGlobally(featuremgmt.FlagExternalCorePlugins) { return bundles, nil } diff --git a/pkg/services/pluginsintegration/plugins_integration_test.go b/pkg/services/pluginsintegration/plugins_integration_test.go index 5018794a743..c90dd86a2a6 100644 --- a/pkg/services/pluginsintegration/plugins_integration_test.go +++ b/pkg/services/pluginsintegration/plugins_integration_test.go @@ -75,7 +75,7 @@ func TestIntegrationPluginManager(t *testing.T) { Azure: &azsettings.AzureSettings{}, // nolint:staticcheck - IsFeatureToggleEnabled: features.IsEnabled, + IsFeatureToggleEnabled: features.IsEnabledGlobally, } tracer := tracing.InitializeTracerForTest() diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index e83b04cccb5..eee838fda52 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -152,7 +152,7 @@ func NewClientDecorator( func CreateMiddlewares(cfg *setting.Cfg, oAuthTokenService oauthtoken.OAuthTokenService, tracer tracing.Tracer, cachingService caching.CachingService, features *featuremgmt.FeatureManager, promRegisterer prometheus.Registerer, registry registry.Service) []plugins.ClientMiddleware { var middlewares []plugins.ClientMiddleware - if features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { + if features.IsEnabledGlobally(featuremgmt.FlagPluginsInstrumentationStatusSource) { middlewares = []plugins.ClientMiddleware{ clientmiddleware.NewPluginRequestMetaMiddleware(), } @@ -172,11 +172,11 @@ func CreateMiddlewares(cfg *setting.Cfg, oAuthTokenService oauthtoken.OAuthToken ) // Placing the new service implementation behind a feature flag until it is known to be stable - if features.IsEnabled(featuremgmt.FlagUseCachingService) { + if features.IsEnabledGlobally(featuremgmt.FlagUseCachingService) { middlewares = append(middlewares, clientmiddleware.NewCachingMiddlewareWithFeatureManager(cachingService, features)) } - if features.IsEnabled(featuremgmt.FlagIdForwarding) { + if features.IsEnabledGlobally(featuremgmt.FlagIdForwarding) { middlewares = append(middlewares, clientmiddleware.NewForwardIDMiddleware()) } @@ -186,7 +186,7 @@ func CreateMiddlewares(cfg *setting.Cfg, oAuthTokenService oauthtoken.OAuthToken middlewares = append(middlewares, clientmiddleware.NewHTTPClientMiddleware()) - if features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { + if features.IsEnabledGlobally(featuremgmt.FlagPluginsInstrumentationStatusSource) { // StatusSourceMiddleware should be at the very bottom, or any middlewares below it won't see the // correct status source in their context.Context middlewares = append(middlewares, clientmiddleware.NewStatusSourceMiddleware()) diff --git a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go index aee54ca352c..f0bbd4da726 100644 --- a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go +++ b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go @@ -23,7 +23,7 @@ type Service struct { func ProvideService(cfg *config.Cfg, reg extsvcauth.ExternalServiceRegistry, settingsSvc pluginsettings.Service) *Service { s := &Service{ - featureEnabled: cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAuth) || cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAccounts), + featureEnabled: cfg.Features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) || cfg.Features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts), log: log.New("plugins.external.registration"), reg: reg, settingsSvc: settingsSvc, diff --git a/pkg/services/publicdashboards/api/api.go b/pkg/services/publicdashboards/api/api.go index f714f348ce2..6dfaf7babeb 100644 --- a/pkg/services/publicdashboards/api/api.go +++ b/pkg/services/publicdashboards/api/api.go @@ -1,9 +1,11 @@ package api import ( + "context" "net/http" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/log" @@ -41,7 +43,7 @@ func ProvideApi( } // attach api if PublicDashboards feature flag is enabled - if features.IsEnabled(featuremgmt.FlagPublicDashboards) { + if features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) { api.RegisterAPIEndpoints() } @@ -284,9 +286,9 @@ func (api *Api) DeletePublicDashboard(c *contextmodel.ReqContext) response.Respo } // Copied from pkg/api/metrics.go -func toJsonStreamingResponse(features *featuremgmt.FeatureManager, qdr *backend.QueryDataResponse) response.Response { +func toJsonStreamingResponse(ctx context.Context, features *featuremgmt.FeatureManager, qdr *backend.QueryDataResponse) response.Response { statusWhenError := http.StatusBadRequest - if features.IsEnabled(featuremgmt.FlagDatasourceQueryMultiStatus) { + if features.IsEnabled(ctx, featuremgmt.FlagDatasourceQueryMultiStatus) { statusWhenError = http.StatusMultiStatus } diff --git a/pkg/services/publicdashboards/api/query.go b/pkg/services/publicdashboards/api/query.go index 206df58ba2d..e6dabff13d6 100644 --- a/pkg/services/publicdashboards/api/query.go +++ b/pkg/services/publicdashboards/api/query.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" @@ -71,7 +72,7 @@ func (api *Api) QueryPublicDashboard(c *contextmodel.ReqContext) response.Respon return response.Err(err) } - return toJsonStreamingResponse(api.Features, resp) + return toJsonStreamingResponse(c.Req.Context(), api.Features, resp) } // swagger:route GET /public/dashboards/{accessToken}/annotations dashboard_public getPublicAnnotations diff --git a/pkg/services/rendering/auth.go b/pkg/services/rendering/auth.go index 73c43c3b002..450d99d241c 100644 --- a/pkg/services/rendering/auth.go +++ b/pkg/services/rendering/auth.go @@ -37,7 +37,7 @@ func (rs *RenderingService) GetRenderUser(ctx context.Context, key string) (*Ren var renderUser *RenderUser - if looksLikeJWT(key) && rs.features.IsEnabled(featuremgmt.FlagRenderAuthJWT) { + if looksLikeJWT(key) && rs.features.IsEnabled(ctx, featuremgmt.FlagRenderAuthJWT) { from = "jwt" renderUser = rs.getRenderUserFromJWT(key) } else { diff --git a/pkg/services/rendering/rendering.go b/pkg/services/rendering/rendering.go index 930c452dc66..45b38e6e83d 100644 --- a/pkg/services/rendering/rendering.go +++ b/pkg/services/rendering/rendering.go @@ -84,7 +84,7 @@ func ProvideService(cfg *setting.Cfg, features *featuremgmt.FeatureManager, remo } var renderKeyProvider renderKeyProvider - if features.IsEnabled(featuremgmt.FlagRenderAuthJWT) { + if features.IsEnabledGlobally(featuremgmt.FlagRenderAuthJWT) { renderKeyProvider = &jwtRenderKeyProvider{ log: logger, authToken: []byte(cfg.RendererAuthToken), diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index e27ac7691e2..0b2c0e63867 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -116,7 +116,7 @@ func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEv } func (s *StandardSearchService) IsDisabled() bool { - return !s.features.IsEnabled(featuremgmt.FlagPanelTitleSearch) + return !s.features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch) } func (s *StandardSearchService) Run(ctx context.Context) error { diff --git a/pkg/services/secrets/kvstore/migrations/datasource_mig.go b/pkg/services/secrets/kvstore/migrations/datasource_mig.go index 3953fe0b60d..de09f26bc9a 100644 --- a/pkg/services/secrets/kvstore/migrations/datasource_mig.go +++ b/pkg/services/secrets/kvstore/migrations/datasource_mig.go @@ -44,7 +44,7 @@ func (s *DataSourceSecretMigrationService) Migrate(ctx context.Context) error { } logger.Debug(fmt.Sprint("secret migration status is ", migrationStatus)) // If this flag is true, delete secrets from the legacy secrets store as they are migrated - disableSecretsCompatibility := s.features.IsEnabled(featuremgmt.FlagDisableSecretsCompatibility) + disableSecretsCompatibility := s.features.IsEnabled(ctx, featuremgmt.FlagDisableSecretsCompatibility) // If migration hasn't happened, migrate to unified secrets and keep copy in legacy // If a complete migration happened and now backwards compatibility is enabled, copy secrets back to legacy needCompatibility := migrationStatus != compatibleSecretMigrationValue && !disableSecretsCompatibility diff --git a/pkg/services/secrets/kvstore/plugin.go b/pkg/services/secrets/kvstore/plugin.go index d1b96771007..a944da822f2 100644 --- a/pkg/services/secrets/kvstore/plugin.go +++ b/pkg/services/secrets/kvstore/plugin.go @@ -48,7 +48,7 @@ func NewPluginSecretsKVStore( secretsService: secretsService, log: logger, kvstore: kvstore, - backwardsCompatibilityDisabled: features.IsEnabled(featuremgmt.FlagDisableSecretsCompatibility), + backwardsCompatibilityDisabled: features.IsEnabledGlobally(featuremgmt.FlagDisableSecretsCompatibility), fallbackStore: fallback, } } diff --git a/pkg/services/secrets/kvstore/test_helpers.go b/pkg/services/secrets/kvstore/test_helpers.go index 726760c426b..c790446a7b3 100644 --- a/pkg/services/secrets/kvstore/test_helpers.go +++ b/pkg/services/secrets/kvstore/test_helpers.go @@ -147,7 +147,11 @@ func NewFakeFeatureToggles(t *testing.T, returnValue bool) featuremgmt.FeatureTo } } -func (f fakeFeatureToggles) IsEnabled(feature string) bool { +func (f fakeFeatureToggles) IsEnabledGlobally(feature string) bool { + return f.returnValue +} + +func (f fakeFeatureToggles) IsEnabled(ctx context.Context, feature string) bool { return f.returnValue } diff --git a/pkg/services/secrets/manager/manager.go b/pkg/services/secrets/manager/manager.go index c60c7407591..669e7217c98 100644 --- a/pkg/services/secrets/manager/manager.go +++ b/pkg/services/secrets/manager/manager.go @@ -79,7 +79,7 @@ func ProvideSecretsService( log: log.New("secrets"), } - enabled := !features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) + enabled := !features.IsEnabledGlobally(featuremgmt.FlagDisableEnvelopeEncryption) if enabled { err := s.InitProviders() @@ -112,12 +112,12 @@ func (s *SecretsService) InitProviders() (err error) { } func (s *SecretsService) registerUsageMetrics() { - s.usageStats.RegisterMetricsFunc(func(context.Context) (map[string]any, error) { + s.usageStats.RegisterMetricsFunc(func(ctx context.Context) (map[string]any, error) { usageMetrics := make(map[string]any) // Enabled / disabled usageMetrics["stats.encryption.envelope_encryption_enabled.count"] = 0 - if !s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + if !s.features.IsEnabled(ctx, featuremgmt.FlagDisableEnvelopeEncryption) { usageMetrics["stats.encryption.envelope_encryption_enabled.count"] = 1 } @@ -159,7 +159,7 @@ var b64 = base64.RawStdEncoding func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) { // Use legacy encryption service if featuremgmt.FlagDisableEnvelopeEncryption toggle is on - if s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + if s.features.IsEnabled(ctx, featuremgmt.FlagDisableEnvelopeEncryption) { return s.enc.Encrypt(ctx, payload, setting.SecretKey) } @@ -333,7 +333,7 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e // If encrypted with envelope encryption, the feature is disabled and // no provider is initialized, then we throw an error. if s.encryptedWithEnvelopeEncryption(payload) && - s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) && + s.features.IsEnabled(ctx, featuremgmt.FlagDisableEnvelopeEncryption) && !s.providersInitialized() { err = fmt.Errorf("failed to decrypt a secret encrypted with envelope encryption: envelope encryption is disabled") return nil, err @@ -469,7 +469,7 @@ func (s *SecretsService) RotateDataKeys(ctx context.Context) error { func (s *SecretsService) ReEncryptDataKeys(ctx context.Context) error { s.log.Info("Data keys re-encryption triggered") - if s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + if s.features.IsEnabled(ctx, featuremgmt.FlagDisableEnvelopeEncryption) { s.log.Info("Envelope encryption is not enabled but trying to init providers anyway...") if err := s.InitProviders(); err != nil { diff --git a/pkg/services/secrets/migrator/migrator.go b/pkg/services/secrets/migrator/migrator.go index 75ff7de755c..3361bc097f8 100644 --- a/pkg/services/secrets/migrator/migrator.go +++ b/pkg/services/secrets/migrator/migrator.go @@ -114,7 +114,7 @@ func (m *SecretsMigrator) RollBackSecrets(ctx context.Context) (bool, error) { } func (m *SecretsMigrator) initProvidersIfNeeded() error { - if m.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + if m.features.IsEnabledGlobally(featuremgmt.FlagDisableEnvelopeEncryption) { logger.Info("Envelope encryption is not enabled but trying to init providers anyway...") if err := m.secretsSrv.InitProviders(); err != nil { diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index 4932c83a061..98abc9eb0c9 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -48,7 +48,7 @@ func NewServiceAccountsAPI( RouterRegister: routerRegister, log: log.New("serviceaccounts.api"), permissionService: permissionService, - isExternalSAEnabled: features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabled(featuremgmt.FlagExternalServiceAuth), + isExternalSAEnabled: features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth), } } diff --git a/pkg/services/serviceaccounts/extsvcaccounts/service.go b/pkg/services/serviceaccounts/extsvcaccounts/service.go index 85c5a26c951..dd0fcb09683 100644 --- a/pkg/services/serviceaccounts/extsvcaccounts/service.go +++ b/pkg/services/serviceaccounts/extsvcaccounts/service.go @@ -41,7 +41,7 @@ func ProvideExtSvcAccountsService(acSvc ac.Service, bus bus.Bus, db db.DB, featu skvStore: kvstore.NewSQLSecretsKVStore(db, secretsSvc, logger), // Using SQL store to avoid a cyclic dependency } - if features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) { // Register the metrics esa.metrics = newMetrics(reg, saSvc, logger) @@ -95,7 +95,7 @@ func (esa *ExtSvcAccountsService) RetrieveExtSvcAccount(ctx context.Context, org // SaveExternalService creates, updates or delete a service account (and its token) with the requested permissions. func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd *extsvcauth.ExternalServiceRegistration) (*extsvcauth.ExternalService, error) { // This is double proofing, we should never reach here anyway the flags have already been checked. - if !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAuth) { esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services") return nil, nil } @@ -140,7 +140,7 @@ func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd * func (esa *ExtSvcAccountsService) RemoveExternalService(ctx context.Context, name string) error { // This is double proofing, we should never reach here anyway the flags have already been checked. - if !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAuth) { esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services") return nil } @@ -172,7 +172,7 @@ func (esa *ExtSvcAccountsService) RemoveExtSvcAccount(ctx context.Context, orgID // ManageExtSvcAccount creates, updates or deletes the service account associated with an external service func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *sa.ManageExtSvcAccountCmd) (int64, error) { // This is double proofing, we should never reach here anyway the flags have already been checked. - if !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + if !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAuth) { esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services") return 0, nil } diff --git a/pkg/services/serviceaccounts/proxy/service.go b/pkg/services/serviceaccounts/proxy/service.go index 944ba3666da..beabbd3d29c 100644 --- a/pkg/services/serviceaccounts/proxy/service.go +++ b/pkg/services/serviceaccounts/proxy/service.go @@ -38,7 +38,7 @@ func ProvideServiceAccountsProxy( s := &ServiceAccountsProxy{ log: log.New("serviceaccounts.proxy"), proxiedService: proxiedService, - isProxyEnabled: features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabled(featuremgmt.FlagExternalServiceAuth), + isProxyEnabled: features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth), } serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, permissionService, features) diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go index 612ad9833ad..67f8eb696d1 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go @@ -184,7 +184,7 @@ func TestMigrations(t *testing.T) { { desc: "without editors can admin", // nolint:staticcheck - config: setting.NewCfgWithFeatures(featuremgmt.WithFeatures("accesscontrol").IsEnabled), + config: setting.NewCfgWithFeatures(featuremgmt.WithFeatures("accesscontrol").IsEnabledGlobally), expectedRolePerms: map[string][]rawPermission{ "managed:users:1:permissions": {{Action: "teams:read", Scope: team1Scope}}, "managed:users:2:permissions": {{Action: "teams:read", Scope: team1Scope}}, diff --git a/pkg/services/sqlstore/permissions/dashboard.go b/pkg/services/sqlstore/permissions/dashboard.go index af8852d930f..1d1465c06aa 100644 --- a/pkg/services/sqlstore/permissions/dashboard.go +++ b/pkg/services/sqlstore/permissions/dashboard.go @@ -84,7 +84,7 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi } var f PermissionsFilter - if features.IsEnabled(featuremgmt.FlagPermissionsFilterRemoveSubquery) { + if features.IsEnabledGlobally(featuremgmt.FlagPermissionsFilterRemoveSubquery) { f = &accessControlDashboardPermissionFilterNoFolderSubquery{ accessControlDashboardPermissionFilter: accessControlDashboardPermissionFilter{ user: user, folderActions: folderActions, dashboardActions: dashboardActions, features: features, @@ -201,7 +201,7 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { } permSelector.WriteRune(')') - switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { + switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) { case true: if len(permSelectorArgs) > 0 { switch f.recursiveQueriesAreSupported { @@ -280,7 +280,7 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { permSelector.WriteRune(')') - switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { + switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) { case true: if len(permSelectorArgs) > 0 { switch f.recursiveQueriesAreSupported { diff --git a/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go b/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go index e4b13db9c44..ae362990167 100644 --- a/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go +++ b/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go @@ -116,7 +116,7 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() permSelector.WriteRune(')') - switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { + switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) { case true: if len(permSelectorArgs) > 0 { switch f.recursiveQueriesAreSupported { @@ -193,7 +193,7 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() } permSelector.WriteRune(')') - switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { + switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) { case true: if len(permSelectorArgs) > 0 { switch f.recursiveQueriesAreSupported { diff --git a/pkg/services/sqlstore/searchstore/builder.go b/pkg/services/sqlstore/searchstore/builder.go index 38c4ec0fa94..675a9fe92d5 100644 --- a/pkg/services/sqlstore/searchstore/builder.go +++ b/pkg/services/sqlstore/searchstore/builder.go @@ -37,7 +37,7 @@ func (b *Builder) ToSQL(limit, page int64) (string, []any) { INNER JOIN dashboard ON ids.id = dashboard.id`) b.sql.WriteString("\n") - if b.Features.IsEnabled(featuremgmt.FlagNestedFolders) { + if b.Features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) { b.sql.WriteString( `LEFT OUTER JOIN folder ON folder.uid = dashboard.folder_uid AND folder.org_id = dashboard.org_id`) } else { @@ -67,7 +67,7 @@ func (b *Builder) buildSelect() { dashboard.folder_id, folder.uid AS folder_uid, `) - if b.Features.IsEnabled(featuremgmt.FlagNestedFolders) { + if b.Features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) { b.sql.WriteString(` folder.title AS folder_slug,`) } else { diff --git a/pkg/services/sqlstore/sqlstore_test.go b/pkg/services/sqlstore/sqlstore_test.go index 8b696120435..83154c62eef 100644 --- a/pkg/services/sqlstore/sqlstore_test.go +++ b/pkg/services/sqlstore/sqlstore_test.go @@ -177,7 +177,7 @@ func makeSQLStoreTestConfig(t *testing.T, tc sqlStoreTest) *setting.Cfg { tc.features = featuremgmt.WithFeatures() } // nolint:staticcheck - cfg := setting.NewCfgWithFeatures(tc.features.IsEnabled) + cfg := setting.NewCfgWithFeatures(tc.features.IsEnabledGlobally) sec, err := cfg.Raw.NewSection("database") require.NoError(t, err) diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index 20bbdfe7626..b3d0d284f04 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -45,7 +45,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, fbStrategies: strategies, } - if features.IsEnabled(featuremgmt.FlagSsoSettingsApi) { + if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) { ssoSettingsApi := api.ProvideApi(svc, routeRegister, ac) ssoSettingsApi.RegisterAPIEndpoints() } diff --git a/pkg/services/store/entity/migrations/migrator.go b/pkg/services/store/entity/migrations/migrator.go index 7702f38acef..e688eaa663d 100644 --- a/pkg/services/store/entity/migrations/migrator.go +++ b/pkg/services/store/entity/migrations/migrator.go @@ -14,7 +14,7 @@ import ( func MigrateEntityStore(xdb db.DB, features featuremgmt.FeatureToggles) error { // Skip if feature flag is not enabled - if !features.IsEnabled(featuremgmt.FlagEntityStore) { + if !features.IsEnabledGlobally(featuremgmt.FlagEntityStore) { return nil } @@ -67,6 +67,6 @@ func MigrateEntityStore(xdb db.DB, features featuremgmt.FeatureToggles) error { } return mg.Start( - features.IsEnabled(featuremgmt.FlagMigrationLocking), + features.IsEnabledGlobally(featuremgmt.FlagMigrationLocking), sql.GetMigrationLockAttemptTimeout()) } diff --git a/pkg/services/store/entity_events.go b/pkg/services/store/entity_events.go index 6dd2b03a186..0d5030c3c6b 100644 --- a/pkg/services/store/entity_events.go +++ b/pkg/services/store/entity_events.go @@ -70,7 +70,7 @@ type EntityEventsService interface { } func ProvideEntityEventsService(cfg *setting.Cfg, sqlStore db.DB, features featuremgmt.FeatureToggles) EntityEventsService { - if !features.IsEnabled(featuremgmt.FlagPanelTitleSearch) { + if !features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch) { return &dummyEntityEventsService{} } diff --git a/pkg/tsdb/cloudwatch/log_actions.go b/pkg/tsdb/cloudwatch/log_actions.go index cb0e4f43de9..7da3635cfd4 100644 --- a/pkg/tsdb/cloudwatch/log_actions.go +++ b/pkg/tsdb/cloudwatch/log_actions.go @@ -212,7 +212,7 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c QueryString: aws.String(modifiedQueryString), } - if logsQuery.LogGroups != nil && len(logsQuery.LogGroups) > 0 && e.features.IsEnabled(featuremgmt.FlagCloudWatchCrossAccountQuerying) { + if logsQuery.LogGroups != nil && len(logsQuery.LogGroups) > 0 && e.features.IsEnabled(ctx, featuremgmt.FlagCloudWatchCrossAccountQuerying) { var logGroupIdentifiers []string for _, lg := range logsQuery.LogGroups { arn := lg.Arn diff --git a/pkg/tsdb/cloudwatch/mocks/logs.go b/pkg/tsdb/cloudwatch/mocks/logs.go index 6b3962703b0..e0b16bf436d 100644 --- a/pkg/tsdb/cloudwatch/mocks/logs.go +++ b/pkg/tsdb/cloudwatch/mocks/logs.go @@ -7,8 +7,9 @@ import ( "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" - "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" "github.com/stretchr/testify/mock" + + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" ) type LogsAPI struct { @@ -43,16 +44,6 @@ func (l *LogsService) GetLogGroupFieldsWithContext(ctx context.Context, request return args.Get(0).([]resources.ResourceResponse[resources.LogGroupField]), args.Error(1) } -type MockFeatures struct { - mock.Mock -} - -func (f *MockFeatures) IsEnabled(feature string) bool { - args := f.Called(feature) - - return args.Bool(0) -} - type MockLogEvents struct { cloudwatchlogsiface.CloudWatchLogsAPI diff --git a/pkg/tsdb/cloudwatch/resource_handler.go b/pkg/tsdb/cloudwatch/resource_handler.go index 5f154830891..cedbcb2addc 100644 --- a/pkg/tsdb/cloudwatch/resource_handler.go +++ b/pkg/tsdb/cloudwatch/resource_handler.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/routes" ) @@ -28,7 +29,7 @@ func (e *cloudWatchExecutor) newResourceMux() *http.ServeMux { mux.HandleFunc("/external-id", routes.ResourceRequestMiddleware(routes.ExternalIdHandler, logger, e.getRequestContext)) // feature is enabled by default, just putting behind a feature flag in case of unexpected bugs - if e.features.IsEnabled("cloudwatchNewRegionsHandler") { + if e.features.IsEnabledGlobally(featuremgmt.FlagCloudwatchNewRegionsHandler) { mux.HandleFunc("/regions", routes.ResourceRequestMiddleware(routes.RegionsHandler, logger, e.getRequestContext)) } else { mux.HandleFunc("/regions", handleResourceReq(e.handleGetRegions)) diff --git a/pkg/tsdb/cloudwatch/routes/log_group_fields_test.go b/pkg/tsdb/cloudwatch/routes/log_group_fields_test.go index 84f5c17681c..70e13b71035 100644 --- a/pkg/tsdb/cloudwatch/routes/log_group_fields_test.go +++ b/pkg/tsdb/cloudwatch/routes/log_group_fields_test.go @@ -8,17 +8,19 @@ import ( "testing" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func TestLogGroupFieldsRoute(t *testing.T) { - mockFeatures := mocks.MockFeatures{} + mockFeatures := featuremgmt.WithFeatures() reqCtxFunc := func(_ context.Context, pluginCtx backend.PluginContext, region string) (reqCtx models.RequestContext, err error) { - return models.RequestContext{Features: &mockFeatures}, err + return models.RequestContext{Features: mockFeatures}, err } t.Run("returns 400 if an invalid LogGroupFieldsRequest is used", func(t *testing.T) { rr := httptest.NewRecorder() diff --git a/pkg/tsdb/cloudwatch/routes/log_groups.go b/pkg/tsdb/cloudwatch/routes/log_groups.go index 0ed15c70180..92b2cd6039e 100644 --- a/pkg/tsdb/cloudwatch/routes/log_groups.go +++ b/pkg/tsdb/cloudwatch/routes/log_groups.go @@ -7,6 +7,7 @@ import ( "net/url" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" @@ -46,5 +47,5 @@ var newLogGroupsService = func(ctx context.Context, pluginCtx backend.PluginCont return nil, err } - return services.NewLogGroupsService(reqCtx.LogsAPIProvider, reqCtx.Features.IsEnabled(featuremgmt.FlagCloudWatchCrossAccountQuerying)), nil + return services.NewLogGroupsService(reqCtx.LogsAPIProvider, reqCtx.Features.IsEnabled(ctx, featuremgmt.FlagCloudWatchCrossAccountQuerying)), nil } diff --git a/pkg/tsdb/cloudwatch/routes/log_groups_test.go b/pkg/tsdb/cloudwatch/routes/log_groups_test.go index 8a6d88613d6..fad9cc272bf 100644 --- a/pkg/tsdb/cloudwatch/routes/log_groups_test.go +++ b/pkg/tsdb/cloudwatch/routes/log_groups_test.go @@ -8,13 +8,14 @@ import ( "testing" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func TestLogGroupsRoute(t *testing.T) { @@ -23,10 +24,9 @@ func TestLogGroupsRoute(t *testing.T) { newLogGroupsService = origLogGroupsService }) - mockFeatures := mocks.MockFeatures{} - mockFeatures.On("IsEnabled", featuremgmt.FlagCloudWatchCrossAccountQuerying).Return(false) + mockFeatures := featuremgmt.WithFeatures() reqCtxFunc := func(_ context.Context, pluginCtx backend.PluginContext, region string) (reqCtx models.RequestContext, err error) { - return models.RequestContext{Features: &mockFeatures}, err + return models.RequestContext{Features: mockFeatures}, err } t.Run("successfully returns 1 log group with account id", func(t *testing.T) { diff --git a/pkg/tsdb/cloudwatch/time_series_query.go b/pkg/tsdb/cloudwatch/time_series_query.go index 96f7bbd1880..a228f571211 100644 --- a/pkg/tsdb/cloudwatch/time_series_query.go +++ b/pkg/tsdb/cloudwatch/time_series_query.go @@ -37,7 +37,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger } requestQueries, err := models.ParseMetricDataQueries(req.Queries, startTime, endTime, instance.Settings.Region, logger, - e.features.IsEnabled(featuremgmt.FlagCloudWatchCrossAccountQuerying)) + e.features.IsEnabled(ctx, featuremgmt.FlagCloudWatchCrossAccountQuerying)) if err != nil { return nil, err } @@ -60,7 +60,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger region := r batches := [][]*models.CloudWatchQuery{regionQueries} - if e.features.IsEnabled(featuremgmt.FlagCloudWatchBatchQueries) { + if e.features.IsEnabled(ctx, featuremgmt.FlagCloudWatchBatchQueries) { batches = getMetricQueryBatches(regionQueries, logger) } @@ -95,7 +95,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger return err } - if e.features.IsEnabled(featuremgmt.FlagCloudWatchWildCardDimensionValues) { + if e.features.IsEnabled(ctx, featuremgmt.FlagCloudWatchWildCardDimensionValues) { requestQueries, err = e.getDimensionValuesForWildcards(ctx, req.PluginContext, region, client, requestQueries, instance.tagValueCache, logger) if err != nil { return err diff --git a/pkg/tsdb/loki/loki.go b/pkg/tsdb/loki/loki.go index ceb5b1d08c7..138da10fdb8 100644 --- a/pkg/tsdb/loki/loki.go +++ b/pkg/tsdb/loki/loki.go @@ -173,11 +173,11 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) } responseOpts := ResponseOpts{ - metricDataplane: s.features.IsEnabled(featuremgmt.FlagLokiMetricDataplane), - logsDataplane: s.features.IsEnabled(featuremgmt.FlagLokiLogsDataplane), + metricDataplane: s.features.IsEnabled(ctx, featuremgmt.FlagLokiMetricDataplane), + logsDataplane: s.features.IsEnabled(ctx, featuremgmt.FlagLokiLogsDataplane), } - return queryData(ctx, req, dsInfo, responseOpts, s.tracer, logger, s.features.IsEnabled(featuremgmt.FlagLokiRunQueriesInParallel)) + return queryData(ctx, req, dsInfo, responseOpts, s.tracer, logger, s.features.IsEnabled(ctx, featuremgmt.FlagLokiRunQueriesInParallel)) } func queryData(ctx context.Context, req *backend.QueryDataRequest, dsInfo *datasourceInfo, responseOpts ResponseOpts, tracer tracing.Tracer, plog log.Logger, runInParallel bool) (*backend.QueryDataResponse, error) { diff --git a/pkg/tsdb/prometheus/querydata/request.go b/pkg/tsdb/prometheus/querydata/request.go index 0307a984b46..762cf371cd4 100644 --- a/pkg/tsdb/prometheus/querydata/request.go +++ b/pkg/tsdb/prometheus/querydata/request.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/prometheus/client" @@ -73,7 +74,7 @@ func New( // standard deviation sampler is the default for backwards compatibility exemplarSampler := exemplar.NewStandardDeviationSampler - if features.IsEnabled(featuremgmt.FlagDisablePrometheusExemplarSampling) { + if features.IsEnabledGlobally(featuremgmt.FlagDisablePrometheusExemplarSampling) { exemplarSampler = exemplar.NewNoOpSampler } @@ -85,7 +86,7 @@ func New( TimeInterval: timeInterval, ID: settings.ID, URL: settings.URL, - enableDataplane: features.IsEnabled(featuremgmt.FlagPrometheusDataplane), + enableDataplane: features.IsEnabledGlobally(featuremgmt.FlagPrometheusDataplane), exemplarSampler: exemplarSampler, }, nil } diff --git a/pkg/tsdb/prometheus/querydata/request_test.go b/pkg/tsdb/prometheus/querydata/request_test.go index 82a701db46a..40d3d783b76 100644 --- a/pkg/tsdb/prometheus/querydata/request_test.go +++ b/pkg/tsdb/prometheus/querydata/request_test.go @@ -15,6 +15,7 @@ import ( p "github.com/prometheus/common/model" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery" "github.com/grafana/kindsys" @@ -23,6 +24,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/prometheus/client" "github.com/grafana/grafana/pkg/tsdb/prometheus/models" @@ -440,8 +442,7 @@ func setup() (*testContext, error) { JSONData: json.RawMessage(`{"timeInterval": "15s"}`), } - features := &fakeFeatureToggles{flags: map[string]bool{"prometheusBufferedClient": false}} - + features := featuremgmt.WithFeatures() opts, err := client.CreateTransportOptions(context.Background(), settings, &setting.Cfg{}, log.New()) if err != nil { return nil, err @@ -460,14 +461,6 @@ func setup() (*testContext, error) { }, nil } -type fakeFeatureToggles struct { - flags map[string]bool -} - -func (f *fakeFeatureToggles) IsEnabled(feature string) bool { - return f.flags[feature] -} - type fakeHttpClientProvider struct { httpclient.Provider opts httpclient.Options