FeatureToggles: Add context and and an explicit global check (#78081)

pull/77757/head
Ryan McKinley 2 years ago committed by GitHub
parent c887ef2c9a
commit f69fd3726b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      pkg/api/api.go
  2. 2
      pkg/api/common_test.go
  3. 2
      pkg/api/dashboard.go
  4. 3
      pkg/api/datasources.go
  5. 8
      pkg/api/folder.go
  6. 2
      pkg/api/frontendsettings.go
  7. 2
      pkg/api/frontendsettings_test.go
  8. 6
      pkg/api/index.go
  9. 4
      pkg/api/login.go
  10. 2
      pkg/api/metrics.go
  11. 2
      pkg/api/playlist.go
  12. 2
      pkg/api/pluginproxy/ds_proxy.go
  13. 5
      pkg/api/pluginproxy/pluginproxy.go
  14. 4
      pkg/expr/graph.go
  15. 2
      pkg/expr/nodes.go
  16. 2
      pkg/expr/threshold.go
  17. 2
      pkg/login/social/google_oauth.go
  18. 4
      pkg/login/social/social.go
  19. 4
      pkg/middleware/loggermw/logger.go
  20. 12
      pkg/middleware/request_metrics.go
  21. 2
      pkg/plugins/ifaces.go
  22. 2
      pkg/plugins/manager/fakes/fakes.go
  23. 2
      pkg/plugins/manager/loader/assetpath/assetpath.go
  24. 2
      pkg/registry/apis/example/register.go
  25. 10
      pkg/services/accesscontrol/acimpl/service.go
  26. 2
      pkg/services/accesscontrol/api/api.go
  27. 2
      pkg/services/accesscontrol/resourcepermissions/store.go
  28. 2
      pkg/services/auth/idimpl/service.go
  29. 2
      pkg/services/auth/idimpl/signer.go
  30. 4
      pkg/services/authn/authnimpl/service.go
  31. 4
      pkg/services/authn/clients/session.go
  32. 7
      pkg/services/contexthandler/contexthandler.go
  33. 9
      pkg/services/dashboards/database/database.go
  34. 4
      pkg/services/datasources/service/datasource.go
  35. 2
      pkg/services/extsvcauth/oauthserver/oasimpl/service.go
  36. 8
      pkg/services/extsvcauth/registry/service.go
  37. 7
      pkg/services/featuremgmt/manager.go
  38. 24
      pkg/services/featuremgmt/manager_test.go
  39. 12
      pkg/services/featuremgmt/models.go
  40. 2
      pkg/services/featuremgmt/service.go
  41. 4
      pkg/services/featuremgmt/service_test.go
  42. 8
      pkg/services/folder/folderimpl/folder.go
  43. 2
      pkg/services/grafana-apiserver/config.go
  44. 2
      pkg/services/grpcserver/service.go
  45. 6
      pkg/services/libraryelements/api.go
  46. 2
      pkg/services/libraryelements/database.go
  47. 9
      pkg/services/navtree/navtreeimpl/admin.go
  48. 4
      pkg/services/navtree/navtreeimpl/applinks.go
  49. 4
      pkg/services/navtree/navtreeimpl/navtree.go
  50. 2
      pkg/services/ngalert/api/api_testing.go
  51. 1
      pkg/services/ngalert/migration/store/testing.go
  52. 10
      pkg/services/ngalert/ngalert.go
  53. 2
      pkg/services/ngalert/store/instance_database.go
  54. 2
      pkg/services/ngalert/tests/util.go
  55. 2
      pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go
  56. 2
      pkg/services/pluginsintegration/angularinspector/angularinspector.go
  57. 5
      pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go
  58. 4
      pkg/services/pluginsintegration/clientmiddleware/logger_middleware.go
  59. 4
      pkg/services/pluginsintegration/clientmiddleware/metrics_middleware.go
  60. 2
      pkg/services/pluginsintegration/pipeline/steps.go
  61. 2
      pkg/services/pluginsintegration/plugins_integration_test.go
  62. 8
      pkg/services/pluginsintegration/pluginsintegration.go
  63. 2
      pkg/services/pluginsintegration/serviceregistration/serviceregistration.go
  64. 8
      pkg/services/publicdashboards/api/api.go
  65. 3
      pkg/services/publicdashboards/api/query.go
  66. 2
      pkg/services/rendering/auth.go
  67. 2
      pkg/services/rendering/rendering.go
  68. 2
      pkg/services/searchV2/service.go
  69. 2
      pkg/services/secrets/kvstore/migrations/datasource_mig.go
  70. 2
      pkg/services/secrets/kvstore/plugin.go
  71. 6
      pkg/services/secrets/kvstore/test_helpers.go
  72. 12
      pkg/services/secrets/manager/manager.go
  73. 2
      pkg/services/secrets/migrator/migrator.go
  74. 2
      pkg/services/serviceaccounts/api/api.go
  75. 8
      pkg/services/serviceaccounts/extsvcaccounts/service.go
  76. 2
      pkg/services/serviceaccounts/proxy/service.go
  77. 2
      pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go
  78. 6
      pkg/services/sqlstore/permissions/dashboard.go
  79. 4
      pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go
  80. 4
      pkg/services/sqlstore/searchstore/builder.go
  81. 2
      pkg/services/sqlstore/sqlstore_test.go
  82. 2
      pkg/services/ssosettings/ssosettingsimpl/service.go
  83. 4
      pkg/services/store/entity/migrations/migrator.go
  84. 2
      pkg/services/store/entity_events.go
  85. 2
      pkg/tsdb/cloudwatch/log_actions.go
  86. 13
      pkg/tsdb/cloudwatch/mocks/logs.go
  87. 3
      pkg/tsdb/cloudwatch/resource_handler.go
  88. 10
      pkg/tsdb/cloudwatch/routes/log_group_fields_test.go
  89. 3
      pkg/tsdb/cloudwatch/routes/log_groups.go
  90. 10
      pkg/tsdb/cloudwatch/routes/log_groups_test.go
  91. 6
      pkg/tsdb/cloudwatch/time_series_query.go
  92. 6
      pkg/tsdb/loki/loki.go
  93. 5
      pkg/tsdb/prometheus/querydata/request.go
  94. 13
      pkg/tsdb/prometheus/querydata/request_test.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/orgs/edit/:id", authorizeInOrg(ac.UseGlobalOrg, ac.OrgsAccessEvaluator), hs.Index)
r.Get("/admin/stats", authorize(ac.EvalPermission(ac.ActionServerStatsRead)), 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) 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)
r.Get("/admin/storage/*", reqSignedIn, hs.Index) r.Get("/admin/storage/*", reqSignedIn, hs.Index)
} }
// feature toggle admin page // 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) 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("/dashboards/*", reqSignedIn, hs.Index)
r.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, 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) r.Get("/d-embed", reqSignedIn, middleware.AddAllowEmbeddingHeader(), hs.Index)
} }
if hs.Features.IsEnabled(featuremgmt.FlagPublicDashboards) { if hs.Features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) {
// list public dashboards // list public dashboards
r.Get("/public-dashboards/list", reqSignedIn, hs.Index) r.Get("/public-dashboards/list", reqSignedIn, hs.Index)
@ -216,7 +216,7 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/swagger-ui", swaggerUI) r.Get("/swagger-ui", swaggerUI)
r.Get("/openapi3", openapi3) 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.Post("/api/user/auth-tokens/rotate", routing.Wrap(hs.RotateUserAuthToken))
r.Get("/user/auth-tokens/rotate", routing.Wrap(hs.RotateUserAuthTokenRedirect)) 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)) 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 // Will eventually be replaced with the 'object' route
apiRoute.Group("/storage", hs.StorageService.RegisterHTTPRoutes) 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) 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)
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) { apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
pluginRoute.Post("/:pluginId/install", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin)) 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)) 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)) 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) { apiRoute.Group("/featuremgmt", func(featuremgmtRoute routing.RouteRegister) {
featuremgmtRoute.Get("/state", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.GetFeatureMgmtState) featuremgmtRoute.Get("/state", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.GetFeatureMgmtState)
featuremgmtRoute.Get("/", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.GetFeatureToggles) featuremgmtRoute.Get("/", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.GetFeatureToggles)

@ -228,7 +228,7 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
features = featuremgmt.WithFeatures() features = featuremgmt.WithFeatures()
} }
// nolint:staticcheck // nolint:staticcheck
cfg := setting.NewCfgWithFeatures(features.IsEnabled) cfg := setting.NewCfgWithFeatures(features.IsEnabledGlobally)
return &HTTPServer{ return &HTTPServer{
Cfg: cfg, Cfg: cfg,

@ -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 // If public dashboards is enabled and we have a public dashboard, update meta
// values // 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) publicDashboard, err := hs.PublicDashboardsApi.PublicDashboardService.FindByDashboardUid(c.Req.Context(), c.SignedInUser.GetOrgID(), dash.UID)
if err != nil && !errors.Is(err, publicdashboardModels.ErrPublicDashboardNotFound) { if err != nil && !errors.Is(err, publicdashboardModels.ErrPublicDashboardNotFound) {
return response.Error(http.StatusInternalServerError, "Error while retrieving public dashboards", err) return response.Error(http.StatusInternalServerError, "Error while retrieving public dashboards", err)

@ -15,6 +15,7 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/datasource"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "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 // 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) err := validateTeamHTTPHeaderJSON(jsonData)
if err != nil { if err != nil {
return err return err

@ -43,7 +43,7 @@ const REDACTED = "redacted"
func (hs *HTTPServer) GetFolders(c *contextmodel.ReqContext) response.Response { func (hs *HTTPServer) GetFolders(c *contextmodel.ReqContext) response.Response {
var folders []*folder.Folder var folders []*folder.Folder
var err error 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{ folders, err = hs.folderService.GetChildren(c.Req.Context(), &folder.GetChildrenQuery{
OrgID: c.SignedInUser.GetOrgID(), OrgID: c.SignedInUser.GetOrgID(),
Limit: c.QueryInt64("limit"), Limit: c.QueryInt64("limit"),
@ -190,7 +190,7 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int
} }
isNested := folder.ParentUID != "" isNested := folder.ParentUID != ""
if !isNested || !hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) { if !isNested || !hs.Features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()}, {BuiltinRole: string(org.RoleEditor), Permission: dashboards.PERMISSION_EDIT.String()},
{BuiltinRole: string(org.RoleViewer), Permission: dashboards.PERMISSION_VIEW.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 // 404: notFoundError
// 500: internalServerError // 500: internalServerError
func (hs *HTTPServer) MoveFolder(c *contextmodel.ReqContext) response.Response { 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{} cmd := folder.MoveFolderCommand{}
if err := web.Bind(c.Req, &cmd); err != nil { if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err) 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 return dtos.Folder{}, err
} }
if !hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) { if !hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagNestedFolders) {
return folderDTO, nil return folderDTO, nil
} }

@ -66,7 +66,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
continue continue
} }
if panel.ID == "datagrid" && !hs.Features.IsEnabled(featuremgmt.FlagEnableDatagridEditing) { if panel.ID == "datagrid" && !hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagEnableDatagridEditing) {
continue continue
} }

@ -35,7 +35,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt.
t.Helper() t.Helper()
db.InitTestDB(t) db.InitTestDB(t)
// nolint:staticcheck // nolint:staticcheck
cfg.IsFeatureToggleEnabled = features.IsEnabled cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally
{ {
oldVersion := setting.BuildVersion oldVersion := setting.BuildVersion

@ -38,7 +38,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
return nil, err return nil, err
} }
if hs.Features.IsEnabled(featuremgmt.FlagIndividualCookiePreferences) { if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagIndividualCookiePreferences) {
if !prefs.Cookies("analytics") { if !prefs.Cookies("analytics") {
settings.GoogleAnalytics4Id = "" settings.GoogleAnalytics4Id = ""
settings.GoogleAnalyticsId = "" settings.GoogleAnalyticsId = ""
@ -166,7 +166,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
hs.HooksService.RunIndexDataHooks(&data, c) 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() data.NavTree.Sort()
return &data, nil return &data, nil
@ -244,7 +244,7 @@ func (hs *HTTPServer) getThemeForIndexData(themePrefId string, themeURLParam str
if pref.IsValidThemeID(themePrefId) { if pref.IsValidThemeID(themePrefId) {
theme := pref.GetThemeByID(themePrefId) theme := pref.GetThemeByID(themePrefId)
if !theme.IsExtra || hs.Features.IsEnabled(featuremgmt.FlagExtraThemes) { if !theme.IsExtra || hs.Features.IsEnabledGlobally(featuremgmt.FlagExtraThemes) {
return theme return theme
} }
} }

@ -91,7 +91,7 @@ func (hs *HTTPServer) CookieOptionsFromCfg() cookies.CookieOptions {
} }
func (hs *HTTPServer) LoginView(c *contextmodel.ReqContext) { 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) { if errors.Is(c.LookupTokenErr, authn.ErrTokenNeedsRotation) {
c.Redirect(hs.Cfg.AppSubURL + "/") c.Redirect(hs.Cfg.AppSubURL + "/")
return return
@ -334,7 +334,7 @@ func (hs *HTTPServer) RedirectResponseWithError(c *contextmodel.ReqContext, err
func (hs *HTTPServer) redirectURLWithErrorCookie(c *contextmodel.ReqContext, err error) string { func (hs *HTTPServer) redirectURLWithErrorCookie(c *contextmodel.ReqContext, err error) string {
setCookie := true setCookie := true
if hs.Features.IsEnabled(featuremgmt.FlagIndividualCookiePreferences) { if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagIndividualCookiePreferences) {
var userID int64 var userID int64
if c.SignedInUser != nil && !c.SignedInUser.IsNil() { if c.SignedInUser != nil && !c.SignedInUser.IsNil() {
var errID error var errID error

@ -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 { func (hs *HTTPServer) toJsonStreamingResponse(ctx context.Context, qdr *backend.QueryDataResponse) response.Response {
statusWhenError := http.StatusBadRequest statusWhenError := http.StatusBadRequest
if hs.Features.IsEnabled(featuremgmt.FlagDatasourceQueryMultiStatus) { if hs.Features.IsEnabled(ctx, featuremgmt.FlagDatasourceQueryMultiStatus) {
statusWhenError = http.StatusMultiStatus statusWhenError = http.StatusMultiStatus
} }

@ -26,7 +26,7 @@ import (
func (hs *HTTPServer) registerPlaylistAPI(apiRoute routing.RouteRegister) { func (hs *HTTPServer) registerPlaylistAPI(apiRoute routing.RouteRegister) {
// Register the actual handlers // Register the actual handlers
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) { 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 // Use k8s client to implement legacy API
handler := newPlaylistK8sHandler(hs) handler := newPlaylistK8sHandler(hs)
playlistRoute.Get("/", handler.searchPlaylists) playlistRoute.Get("/", handler.searchPlaylists)

@ -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) proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser)
} }
} }

@ -7,6 +7,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"go.opentelemetry.io/otel/attribute"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" 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"
"github.com/grafana/grafana/pkg/util/proxyutil" "github.com/grafana/grafana/pkg/util/proxyutil"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
"go.opentelemetry.io/otel/attribute"
) )
type PluginProxy struct { type PluginProxy struct {
@ -161,7 +162,7 @@ func (proxy PluginProxy) director(req *http.Request) {
proxyutil.ApplyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser) 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) proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser)
} }

@ -61,7 +61,7 @@ type DataPipeline []Node
func (dp *DataPipeline) execute(c context.Context, now time.Time, s *Service) (mathexp.Vars, error) { func (dp *DataPipeline) execute(c context.Context, now time.Time, s *Service) (mathexp.Vars, error) {
vars := make(mathexp.Vars) 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. // Execute datasource nodes first, and grouped by datasource.
if groupByDSFlag { if groupByDSFlag {
dsNodes := []*DSNode{} dsNodes := []*DSNode{}
@ -227,7 +227,7 @@ func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) {
case TypeCMDNode: case TypeCMDNode:
node, err = buildCMDNode(rn, s.features) node, err = buildCMDNode(rn, s.features)
case TypeMLNode: case TypeMLNode:
if s.features.IsEnabled(featuremgmt.FlagMlExpressions) { if s.features.IsEnabledGlobally(featuremgmt.FlagMlExpressions) {
node, err = s.buildMLNode(dp, rn, req) node, err = s.buildMLNode(dp, rn, req)
if err != nil { if err != nil {
err = fmt.Errorf("fail to parse expression with refID %v: %w", rn.RefID, err) err = fmt.Errorf("fail to parse expression with refID %v: %w", rn.RefID, err)

@ -388,7 +388,7 @@ func convertDataFramesToResults(ctx context.Context, frames data.Frames, datasou
} }
var dt data.FrameType 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 { if useDataplane {
logger.Debug("Handling SSE data source query through dataplane", "datatype", dt) logger.Debug("Handling SSE data source query through dataplane", "datatype", dt)
result, err := handleDataplaneFrames(ctx, s.tracer, dt, frames) result, err := handleDataplaneFrames(ctx, s.tracer, dt, frames)

@ -81,7 +81,7 @@ func UnmarshalThresholdCommand(rn *rawNode, features featuremgmt.FeatureToggles)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid condition: %w", err) 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, err := NewThresholdCommand(rn.RefID, referenceVar, firstCondition.UnloadEvaluator.Type, firstCondition.UnloadEvaluator.Params)
unloading.Invert = true unloading.Invert = true
if err != nil { if err != nil {

@ -134,7 +134,7 @@ func (s *SocialGoogle) extractFromAPI(ctx context.Context, client *http.Client)
} }
func (s *SocialGoogle) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string { 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) opts = append(opts, oauth2.AccessTypeOffline, oauth2.ApprovalForce)
} }
return s.SocialBase.AuthCodeURL(state, opts...) return s.SocialBase.AuthCodeURL(state, opts...)

@ -206,7 +206,7 @@ func ProvideService(cfg *setting.Cfg,
forceUseGraphAPI: sec.Key("force_use_graph_api").MustBool(false), forceUseGraphAPI: sec.Key("force_use_graph_api").MustBool(false),
skipOrgRoleSync: cfg.AzureADSkipOrgRoleSync, skipOrgRoleSync: cfg.AzureADSkipOrgRoleSync,
} }
if info.UseRefreshToken && features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) { if info.UseRefreshToken && features.IsEnabledGlobally(featuremgmt.FlagAccessTokenExpirationCheck) {
appendUniqueScope(&config, OfflineAccessScope) appendUniqueScope(&config, OfflineAccessScope)
} }
} }
@ -219,7 +219,7 @@ func ProvideService(cfg *setting.Cfg,
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
skipOrgRoleSync: cfg.OktaSkipOrgRoleSync, skipOrgRoleSync: cfg.OktaSkipOrgRoleSync,
} }
if info.UseRefreshToken && features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) { if info.UseRefreshToken && features.IsEnabledGlobally(featuremgmt.FlagAccessTokenExpirationCheck) {
appendUniqueScope(&config, OfflineAccessScope) appendUniqueScope(&config, OfflineAccessScope)
} }
} }

@ -64,7 +64,7 @@ func (l *loggerImpl) Middleware() web.Middleware {
// put the start time on context so we can measure it later. // put the start time on context so we can measure it later.
r = r.WithContext(log.InitstartTime(r.Context(), time.Now())) 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())) 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) 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()) rmd := requestmeta.GetRequestMetaData(c.Req.Context())
logParams = append(logParams, "status_source", rmd.StatusSource) logParams = append(logParams, "status_source", rmd.StatusSource)
} }

@ -37,7 +37,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR
histogramLabels := []string{"handler", "status_code", "method"} histogramLabels := []string{"handler", "status_code", "method"}
if features.IsEnabled(featuremgmt.FlagRequestInstrumentationStatusSource) { if features.IsEnabledGlobally(featuremgmt.FlagRequestInstrumentationStatusSource) {
histogramLabels = append(histogramLabels, "status_source") histogramLabels = append(histogramLabels, "status_source")
} }
@ -45,7 +45,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR
histogramLabels = append(histogramLabels, "grafana_team") histogramLabels = append(histogramLabels, "grafana_team")
} }
if features.IsEnabled(featuremgmt.FlagHttpSLOLevels) { if features.IsEnabledGlobally(featuremgmt.FlagHttpSLOLevels) {
histogramLabels = append(histogramLabels, "slo_group") histogramLabels = append(histogramLabels, "slo_group")
} }
@ -56,7 +56,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR
Buckets: defBuckets, Buckets: defBuckets,
} }
if features.IsEnabled(featuremgmt.FlagEnableNativeHTTPHistogram) { if features.IsEnabledGlobally(featuremgmt.FlagEnableNativeHTTPHistogram) {
// the recommended default value from the prom_client // the recommended default value from the prom_client
// https://github.com/prometheus/client_golang/blob/main/prometheus/histogram.go#L411 // 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 // 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" handler = "notfound"
} else { } else {
// log requests where we could not identify handler so we can register them. // 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) 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} labelValues := []string{handler, code, r.Method}
rmd := requestmeta.GetRequestMetaData(r.Context()) rmd := requestmeta.GetRequestMetaData(r.Context())
if features.IsEnabled(featuremgmt.FlagRequestInstrumentationStatusSource) { if features.IsEnabled(r.Context(), featuremgmt.FlagRequestInstrumentationStatusSource) {
labelValues = append(labelValues, string(rmd.StatusSource)) labelValues = append(labelValues, string(rmd.StatusSource))
} }
@ -112,7 +112,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promR
labelValues = append(labelValues, rmd.Team) labelValues = append(labelValues, rmd.Team)
} }
if features.IsEnabled(featuremgmt.FlagHttpSLOLevels) { if features.IsEnabled(r.Context(), featuremgmt.FlagHttpSLOLevels) {
labelValues = append(labelValues, string(rmd.SLOGroup)) labelValues = append(labelValues, string(rmd.SLOGroup))
} }

@ -155,7 +155,7 @@ func (fn ClientMiddlewareFunc) CreateClientMiddleware(next Client) Client {
} }
type FeatureToggles interface { type FeatureToggles interface {
IsEnabled(flag string) bool IsEnabledGlobally(flag string) bool
GetEnabled(ctx context.Context) map[string]bool GetEnabled(ctx context.Context) map[string]bool
} }

@ -594,6 +594,6 @@ func (f *FakeFeatureToggles) GetEnabled(_ context.Context) map[string]bool {
return f.features return f.features
} }
func (f *FakeFeatureToggles) IsEnabled(feature string) bool { func (f *FakeFeatureToggles) IsEnabledGlobally(feature string) bool {
return f.features[feature] return f.features[feature]
} }

@ -59,7 +59,7 @@ func (s *Service) Base(n PluginInfo) (string, error) {
func (s *Service) Module(n PluginInfo) (string, error) { func (s *Service) Module(n PluginInfo) (string, error) {
if n.class == plugins.ClassCore { if n.class == plugins.ClassCore {
if s.cfg.Features != nil && if s.cfg.Features != nil &&
s.cfg.Features.IsEnabled(featuremgmt.FlagExternalCorePlugins) && s.cfg.Features.IsEnabledGlobally(featuremgmt.FlagExternalCorePlugins) &&
filepath.Base(n.dir) == "dist" { filepath.Base(n.dir) == "dist" {
// The core plugin has been built externally, use the module from the dist folder // The core plugin has been built externally, use the module from the dist folder
} else { } else {

@ -37,7 +37,7 @@ type TestingAPIBuilder struct {
} }
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder { 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 return nil // skip registration unless opting into experimental apis
} }
builder := &TestingAPIBuilder{ builder := &TestingAPIBuilder{

@ -43,7 +43,7 @@ func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegis
return nil, err 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 // Migrating scopes that haven't been split yet to have kind, attribute and identifier in the DB
// This will be removed once we've: // This will be removed once we've:
// 1) removed the feature toggle and // 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 // DeclarePluginRoles allow the caller to declare, to the service, plugin roles and their assignments
// to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin" // 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 // Protect behind feature toggle
if !s.features.IsEnabled(featuremgmt.FlagAccessControlOnCall) { if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) {
return nil return nil
} }
@ -397,7 +397,7 @@ func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchO
} }
func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { 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.") s.log.Debug("Registering an external service role is behind a feature flag, enable it to use this feature.")
return nil 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 { 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.") s.log.Debug("Deleting an external service role is behind a feature flag, enable it to use this feature.")
return nil return nil
} }

@ -37,7 +37,7 @@ func (api *AccessControlAPI) RegisterAPIEndpoints() {
api.RouteRegister.Group("/api/access-control", func(rr routing.RouteRegister) { api.RouteRegister.Group("/api/access-control", func(rr routing.RouteRegister) {
rr.Get("/user/actions", middleware.ReqSignedIn, routing.Wrap(api.getUserActions)) rr.Get("/user/actions", middleware.ReqSignedIn, routing.Wrap(api.getUserActions))
rr.Get("/user/permissions", middleware.ReqSignedIn, routing.Wrap(api.getUserPermissions)) 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")) userIDScope := ac.Scope("users", "id", ac.Parameter(":userID"))
rr.Get("/users/permissions/search", authorize(ac.EvalPermission(ac.ActionUsersPermissionsRead)), routing.Wrap(api.searchUsersPermissions)) 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)) rr.Get("/user/:userID/permissions/search", authorize(ac.EvalPermission(ac.ActionUsersPermissionsRead, userIDScope)), routing.Wrap(api.searchUserPermissions))

@ -655,7 +655,7 @@ func (s *store) createPermissions(sess *db.Session, roleID int64, resource, reso
p.RoleID = roleID p.RoleID = roleID
p.Created = time.Now() p.Created = time.Now()
p.Updated = 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() p.Kind, p.Attribute, p.Identifier = p.SplitScope()
} }
permissions = append(permissions, p) permissions = append(permissions, p)

@ -32,7 +32,7 @@ func ProvideService(
) *Service { ) *Service {
s := &Service{cfg: cfg, logger: log.New("id-service"), signer: signer, cache: cache, metrics: newMetrics(reg)} 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) authnService.RegisterPostAuthHook(s.hook, 140)
} }

@ -28,7 +28,7 @@ type LocalSigner struct {
} }
func (s *LocalSigner) SignIDToken(ctx context.Context, claims *auth.IDClaims) (string, error) { 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 return "", nil
} }

@ -132,7 +132,7 @@ func ProvideService(
s.RegisterClient(clients.ProvideJWT(jwtService, cfg)) 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)) s.RegisterClient(clients.ProvideExtendedJWT(userService, cfg, signingKeysService, oauthServer))
} }
@ -159,7 +159,7 @@ func ProvideService(
s.RegisterPostAuthHook(orgUserSyncService.SyncOrgRolesHook, 30) s.RegisterPostAuthHook(orgUserSyncService.SyncOrgRolesHook, 30)
s.RegisterPostAuthHook(userSyncService.SyncLastSeenHook, 120) s.RegisterPostAuthHook(userSyncService.SyncLastSeenHook, 120)
if features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) { if features.IsEnabledGlobally(featuremgmt.FlagAccessTokenExpirationCheck) {
s.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService).SyncOauthTokenHook, 60) s.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService).SyncOauthTokenHook, 60)
} }

@ -55,7 +55,7 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
return nil, err 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) { if token.NeedsRotation(time.Duration(s.cfg.TokenRotationIntervalMinutes) * time.Minute) {
return nil, authn.ErrTokenNeedsRotation.Errorf("token needs to be rotated") 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 { 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 return nil
} }

@ -6,6 +6,9 @@ import (
"errors" "errors"
"net/http" "net/http"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
@ -18,8 +21,6 @@ import (
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web" "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, 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 { func (h *ContextHandler) deleteInvalidCookieEndOfRequestFunc(reqContext *contextmodel.ReqContext) web.BeforeFunc {
return func(w web.ResponseWriter) { return func(w web.ResponseWriter) {
if h.features.IsEnabled(featuremgmt.FlagClientTokenRotation) { if h.features.IsEnabled(reqContext.Req.Context(), featuremgmt.FlagClientTokenRotation) {
return return
} }

@ -66,7 +66,7 @@ func ProvideDashboardStore(sqlStore db.DB, cfg *setting.Cfg, features featuremgm
} }
func (d *dashboardStore) emitEntityEvent() bool { 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) { 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 { 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 var res []dashboards.DashboardSearchProjection

@ -198,7 +198,7 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *datasources.AddDataSou
var err error var err error
cmd.EncryptedSecureJsonData = make(map[string][]byte) 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()) cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope())
if err != nil { if err != nil {
return err return err
@ -689,7 +689,7 @@ func (s *Service) fillWithSecureJSONData(ctx context.Context, cmd *datasources.U
} }
cmd.EncryptedSecureJsonData = make(map[string][]byte) 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()) cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope())
if err != nil { if err != nil {
return err return err

@ -65,7 +65,7 @@ type OAuth2ServiceImpl struct {
func ProvideService(router routing.RouteRegister, bus bus.Bus, db db.DB, cfg *setting.Cfg, 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, extSvcAccSvc serviceaccounts.ExtSvcAccountsService, accessControl ac.AccessControl, acSvc ac.Service, userSvc user.Service,
teamSvc team.Service, keySvc signingkeys.Service, fmgmt *featuremgmt.FeatureManager) (*OAuth2ServiceImpl, error) { teamSvc team.Service, keySvc signingkeys.Service, fmgmt *featuremgmt.FeatureManager) (*OAuth2ServiceImpl, error) {
if !fmgmt.IsEnabled(featuremgmt.FlagExternalServiceAuth) { if !fmgmt.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth) {
return nil, nil return nil, nil
} }
config := &fosite.Config{ config := &fosite.Config{

@ -51,14 +51,14 @@ func (r *Registry) RemoveExternalService(ctx context.Context, name string) error
switch provider { switch provider {
case extsvcauth.ServiceAccounts: 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) r.logger.Debug("Skipping External Service removal, flag disabled", "service", name, "flag", featuremgmt.FlagExternalServiceAccounts)
return nil return nil
} }
r.logger.Debug("Routing External Service removal to the External Service Account service", "service", name) r.logger.Debug("Routing External Service removal to the External Service Account service", "service", name)
return r.saReg.RemoveExternalService(ctx, name) return r.saReg.RemoveExternalService(ctx, name)
case extsvcauth.OAuth2Server: 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) r.logger.Debug("Skipping External Service removal, flag disabled", "service", name, "flag", featuremgmt.FlagExternalServiceAccounts)
return nil return nil
} }
@ -80,14 +80,14 @@ func (r *Registry) SaveExternalService(ctx context.Context, cmd *extsvcauth.Exte
switch cmd.AuthProvider { switch cmd.AuthProvider {
case extsvcauth.ServiceAccounts: 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) r.logger.Warn("Skipping External Service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAccounts)
return nil, nil return nil, nil
} }
r.logger.Debug("Routing the External Service registration to the External Service Account service", "service", cmd.Name) r.logger.Debug("Routing the External Service registration to the External Service Account service", "service", cmd.Name)
return r.saReg.SaveExternalService(ctx, cmd) return r.saReg.SaveExternalService(ctx, cmd)
case extsvcauth.OAuth2Server: 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) r.logger.Warn("Skipping External Service authentication, flag disabled", "service", cmd.Name, "flag", featuremgmt.FlagExternalServiceAuth)
return nil, nil return nil, nil
} }

@ -126,7 +126,12 @@ func (fm *FeatureManager) readFile() error {
} }
// IsEnabled checks if a feature is enabled // 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] return fm.enabled[flag]
} }

@ -10,17 +10,17 @@ import (
func TestFeatureManager(t *testing.T) { func TestFeatureManager(t *testing.T) {
t.Run("check testing stubs", func(t *testing.T) { t.Run("check testing stubs", func(t *testing.T) {
ft := WithFeatures("a", "b", "c") ft := WithFeatures("a", "b", "c")
require.True(t, ft.IsEnabled("a")) require.True(t, ft.IsEnabledGlobally("a"))
require.True(t, ft.IsEnabled("b")) require.True(t, ft.IsEnabledGlobally("b"))
require.True(t, ft.IsEnabled("c")) require.True(t, ft.IsEnabledGlobally("c"))
require.False(t, ft.IsEnabled("d")) require.False(t, ft.IsEnabledGlobally("d"))
require.Equal(t, map[string]bool{"a": true, "b": true, "c": true}, ft.GetEnabled(context.Background())) require.Equal(t, map[string]bool{"a": true, "b": true, "c": true}, ft.GetEnabled(context.Background()))
// Explicit values // Explicit values
ft = WithFeatures("a", true, "b", false) ft = WithFeatures("a", true, "b", false)
require.True(t, ft.IsEnabled("a")) require.True(t, ft.IsEnabledGlobally("a"))
require.False(t, ft.IsEnabled("b")) require.False(t, ft.IsEnabledGlobally("b"))
require.Equal(t, map[string]bool{"a": true}, ft.GetEnabled(context.Background())) require.Equal(t, map[string]bool{"a": true}, ft.GetEnabled(context.Background()))
}) })
@ -37,9 +37,9 @@ func TestFeatureManager(t *testing.T) {
Name: "b", Name: "b",
Expression: "true", Expression: "true",
}) })
require.False(t, ft.IsEnabled("a")) require.False(t, ft.IsEnabledGlobally("a"))
require.True(t, ft.IsEnabled("b")) require.True(t, ft.IsEnabledGlobally("b"))
require.False(t, ft.IsEnabled("c")) // uknown flag require.False(t, ft.IsEnabledGlobally("c")) // uknown flag
// Try changing "requires license" // Try changing "requires license"
ft.registerFlags(FeatureFlag{ ft.registerFlags(FeatureFlag{
@ -49,9 +49,9 @@ func TestFeatureManager(t *testing.T) {
Name: "b", Name: "b",
RequiresLicense: true, // expression is still "true" RequiresLicense: true, // expression is still "true"
}) })
require.False(t, ft.IsEnabled("a")) require.False(t, ft.IsEnabledGlobally("a"))
require.False(t, ft.IsEnabled("b")) require.False(t, ft.IsEnabledGlobally("b"))
require.False(t, ft.IsEnabled("c")) require.False(t, ft.IsEnabledGlobally("c"))
}) })
t.Run("check description and docs configs", func(t *testing.T) { t.Run("check description and docs configs", func(t *testing.T) {

@ -2,11 +2,21 @@ package featuremgmt
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
) )
type FeatureToggles interface { 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 // FeatureFlagStage indicates the quality level

@ -74,7 +74,7 @@ func ProvideManagerService(cfg *setting.Cfg, licensing licensing.Licensing) (*Fe
// Minimum approach to avoid circular dependency // Minimum approach to avoid circular dependency
// nolint:staticcheck // nolint:staticcheck
cfg.IsFeatureToggleEnabled = mgmt.IsEnabled cfg.IsFeatureToggleEnabled = mgmt.IsEnabledGlobally
return mgmt, nil return mgmt, nil
} }

@ -43,8 +43,8 @@ func TestFeatureService(t *testing.T) {
require.NotNil(t, mgmt) require.NotNil(t, mgmt)
// Enterprise features do not fall though automatically // Enterprise features do not fall though automatically
require.False(t, mgmt.IsEnabled("a.yes.default")) require.False(t, mgmt.IsEnabledGlobally("a.yes.default"))
require.False(t, mgmt.IsEnabled("a.yes")) // licensed, but not enabled require.False(t, mgmt.IsEnabledGlobally("a.yes")) // licensed, but not enabled
} }
var ( var (

@ -156,7 +156,7 @@ func (s *Service) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder.
return nil, dashboards.ErrFolderAccessDenied return nil, dashboards.ErrFolderAccessDenied
} }
if !s.features.IsEnabled(featuremgmt.FlagNestedFolders) { if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
return dashFolder, nil 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) { 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 nil, nil
} }
return s.store.GetParents(ctx, q) 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 := dashboards.NewDashboardFolder(cmd.Title)
dashFolder.OrgID = cmd.OrgID 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 // Check that the user is allowed to create a subfolder in this folder
evaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.ParentUID)) evaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(cmd.ParentUID))
hasAccess, evalErr := s.accessControl.Evaluate(ctx, cmd.SignedInUser, evaluator) 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} result := []string{*cmd.UID}
countsMap := make(folder.DescendantCounts, len(s.registry)+1) 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) subfolders, err := s.getNestedFolders(ctx, cmd.OrgID, *cmd.UID)
if err != nil { if err != nil {
logger.Error("failed to get subfolders", "error", err) logger.Error("failed to get subfolders", "error", err)

@ -46,7 +46,7 @@ func newConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles) *config {
host := fmt.Sprintf("%s:%d", ip, port) host := fmt.Sprintf("%s:%d", ip, port)
return &config{ return &config{
enabled: features.IsEnabled(featuremgmt.FlagGrafanaAPIServer), enabled: features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServer),
devMode: cfg.Env == setting.Dev, devMode: cfg.Env == setting.Dev,
dataPath: filepath.Join(cfg.DataPath, "grafana-apiserver"), dataPath: filepath.Join(cfg.DataPath, "grafana-apiserver"),
ip: ip, ip: ip,

@ -45,7 +45,7 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, authe
s := &gPRCServerService{ s := &gPRCServerService{
cfg: cfg, cfg: cfg,
logger: log.New("grpc-server"), 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 // Register the metric here instead of an init() function so that we do

@ -21,7 +21,7 @@ func (l *LibraryElementService) registerAPIEndpoints() {
l.RouteRegister.Group("/api/library-elements", func(entities routing.RouteRegister) { l.RouteRegister.Group("/api/library-elements", func(entities routing.RouteRegister) {
uidScope := ScopeLibraryPanelsProvider.GetResourceScopeUID(ac.Parameter(":uid")) 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.Post("/", authorize(ac.EvalPermission(ActionLibraryPanelsCreate)), routing.Wrap(l.createHandler))
entities.Delete("/:uid", authorize(ac.EvalPermission(ActionLibraryPanelsDelete, uidScope)), routing.Wrap(l.deleteHandler)) entities.Delete("/:uid", authorize(ac.EvalPermission(ActionLibraryPanelsDelete, uidScope)), routing.Wrap(l.deleteHandler))
entities.Get("/", authorize(ac.EvalPermission(ActionLibraryPanelsRead)), routing.Wrap(l.getAllHandler)) 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") 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) filteredPanels, err := l.filterLibraryPanelsByPermission(c, elementsResult.Elements)
if err != nil { if err != nil {
return toLibraryElementError(err, "Failed to evaluate permissions") 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") 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) filteredElements, err := l.filterLibraryPanelsByPermission(c, elements)
if err != nil { if err != nil {
return toLibraryElementError(err, err.Error()) return toLibraryElementError(err, err.Error())

@ -166,7 +166,7 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn
} }
err = l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { 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))) allowed, err := l.AccessControl.Evaluate(c, signedInUser, ac.EvalPermission(ActionLibraryPanelsCreate, dashboards.ScopeFoldersProvider.GetResourceScopeUID(*cmd.FolderUID)))
if !allowed { if !allowed {
return fmt.Errorf("insufficient permissions for creating library panel in folder with UID %s", *cmd.FolderUID) return fmt.Errorf("insufficient permissions for creating library panel in folder with UID %s", *cmd.FolderUID)

@ -12,6 +12,7 @@ import (
func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink, error) { func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink, error) {
var configNodes []*navtree.NavLink var configNodes []*navtree.NavLink
ctx := c.Req.Context()
hasAccess := ac.HasAccess(s.accessControl, c) hasAccess := ac.HasAccess(s.accessControl, c)
hasGlobalAccess := ac.HasGlobalAccess(s.accessControl, s.accesscontrolService, c) hasGlobalAccess := ac.HasGlobalAccess(s.accessControl, s.accesscontrolService, c)
orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead) 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 { if err != nil {
return nil, err 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{ configNodes = append(configNodes, &navtree.NavLink{
Text: "Feature Toggles", Text: "Feature Toggles",
SubTitle: "View and edit 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{ configNodes = append(configNodes, &navtree.NavLink{
Text: "Correlations", Text: "Correlations",
Icon: "gf-glue", 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{ storage := &navtree.NavLink{
Text: "Storage", Text: "Storage",
Id: "storage", Id: "storage",

@ -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 { func (s *ServiceImpl) hasAccessToInclude(c *contextmodel.ReqContext, pluginID string) func(include *plugins.Includes) bool {
hasAccess := ac.HasAccess(s.accessControl, c) hasAccess := ac.HasAccess(s.accessControl, c)
return func(include *plugins.Includes) bool { 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)) { if useRBAC && !hasAccess(ac.EvalPermission(include.Action)) {
s.log.Debug("plugin include is covered by RBAC, user doesn't have access", s.log.Debug("plugin include is covered by RBAC, user doesn't have access",
"plugin", pluginID, "plugin", pluginID,
@ -267,7 +267,7 @@ func (s *ServiceImpl) readNavigationSettings() {
"k6-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightAlertsAndIncidents + 1, Text: "Performance testing", Icon: "k6"}, "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 // if cost management is enabled we want to nest adaptive metrics and log volume explorer under that plugin
// in the admin section // in the admin section
s.navigationAppConfig["grafana-adaptive-metrics-app"] = NavigationAppConfig{SectionID: navtree.NavIDCfg} s.navigationAppConfig["grafana-adaptive-metrics-app"] = NavigationAppConfig{SectionID: navtree.NavIDCfg}

@ -358,7 +358,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt
Icon: "library-panel", Icon: "library-panel",
}) })
if s.features.IsEnabled(featuremgmt.FlagPublicDashboards) { if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPublicDashboards) {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Public dashboards", Text: "Public dashboards",
Id: "dashboards/public", 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{ dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Scenes", Text: "Scenes",
Id: "scenes", Id: "scenes",

@ -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 { 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") return ErrResp(http.StatusNotFound, nil, "Backgtesting API is not enabled")
} }

@ -41,6 +41,7 @@ func NewTestMigrationStore(t *testing.T, sqlStore *sqlstore.SQLStore, cfg *setti
cfg.UnifiedAlerting.BaseInterval = time.Second * 10 cfg.UnifiedAlerting.BaseInterval = time.Second * 10
} }
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally
alertingStore := store.DBstore{ alertingStore := store.DBstore{
SQLStore: sqlStore, SQLStore: sqlStore,
Cfg: cfg.UnifiedAlerting, Cfg: cfg.UnifiedAlerting,

@ -246,9 +246,9 @@ func (ng *AlertNG) init() error {
Images: ng.ImageService, Images: ng.ImageService,
Clock: clk, Clock: clk,
Historian: history, Historian: history,
DoNotSaveNormalState: ng.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoNormalState), DoNotSaveNormalState: ng.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingNoNormalState),
MaxStateSaveConcurrency: ng.Cfg.UnifiedAlerting.MaxStateSaveConcurrency, MaxStateSaveConcurrency: ng.Cfg.UnifiedAlerting.MaxStateSaveConcurrency,
ApplyNoDataAndErrorToAllStates: ng.FeatureToggles.IsEnabled(featuremgmt.FlagAlertingNoDataErrorExecution), ApplyNoDataAndErrorToAllStates: ng.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingNoDataErrorExecution),
Tracer: ng.tracer, Tracer: ng.tracer,
Log: log.New("ngalert.state.manager"), 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 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 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 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 we cannot even treat Loki as a secondary, we must use annotations only.
if backend == historian.BackendTypeMultiple || backend == historian.BackendTypeLoki { if backend == historian.BackendTypeMultiple || backend == historian.BackendTypeLoki {
logger.Info("Forcing Annotation backend due to state history feature toggles") logger.Info("Forcing Annotation backend due to state history feature toggles")
@ -493,7 +493,7 @@ func applyStateHistoryFeatureToggles(cfg *setting.UnifiedAlertingStateHistorySet
} }
return return
} }
if !ft.IsEnabled(featuremgmt.FlagAlertStateHistoryLokiPrimary) { if !ft.IsEnabledGlobally(featuremgmt.FlagAlertStateHistoryLokiPrimary) {
// If we're using multiple backends, Loki must be the secondary. // If we're using multiple backends, Loki must be the secondary.
if backend == historian.BackendTypeMultiple { if backend == historian.BackendTypeMultiple {
logger.Info("Coercing Loki to a secondary backend due to state history feature toggles") logger.Info("Coercing Loki to a secondary backend due to state history feature toggles")
@ -509,7 +509,7 @@ func applyStateHistoryFeatureToggles(cfg *setting.UnifiedAlertingStateHistorySet
} }
return 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 we're not allowed to use Loki only, make it the primary but keep the annotation writes.
if backend == historian.BackendTypeLoki { if backend == historian.BackendTypeLoki {
logger.Info("Forcing dual writes to Loki and Annotations due to state history feature toggles") logger.Info("Forcing dual writes to Loki and Annotations due to state history feature toggles")

@ -30,7 +30,7 @@ func (st DBstore) ListAlertInstances(ctx context.Context, cmd *models.ListAlertI
if cmd.RuleUID != "" { if cmd.RuleUID != "" {
addToQuery(` AND rule_uid = ?`, 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)) 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 { if err := sess.SQL(s.String(), params...).Find(&alertInstances); err != nil {

@ -44,8 +44,6 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
tb.Helper() tb.Helper()
cfg := setting.NewCfg() cfg := setting.NewCfg()
// nolint:staticcheck
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{
BaseInterval: setting.SchedulerBaseInterval, BaseInterval: setting.SchedulerBaseInterval,
} }

@ -220,7 +220,7 @@ func (d *Dynamic) setDetectorsFromCache(ctx context.Context) error {
// IsDisabled returns true if FlagPluginsDynamicAngularDetectionPatterns is not enabled. // IsDisabled returns true if FlagPluginsDynamicAngularDetectionPatterns is not enabled.
func (d *Dynamic) IsDisabled() bool { 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. // randomSkew returns a random time.Duration between 0 and maxSkew.

@ -16,7 +16,7 @@ func ProvideService(cfg *config.Cfg, dynamic *angulardetectorsprovider.Dynamic)
var detectorsProvider angulardetector.DetectorsProvider var detectorsProvider angulardetector.DetectorsProvider
var err error var err error
static := angularinspector.NewDefaultStaticDetectorsProvider() 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} detectorsProvider = angulardetector.SequenceDetectorsProvider{dynamic, static}
} else { } else {
detectorsProvider = static detectorsProvider = static

@ -7,12 +7,13 @@ import (
"github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/infra/log"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/caching" "github.com/grafana/grafana/pkg/services/caching"
"github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/prometheus/client_golang/prometheus"
) )
// needed to mock the function for testing // 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 // Update the query cache with the result for this metrics request
if err == nil && cr.UpdateCacheFn != nil { if err == nil && cr.UpdateCacheFn != nil {
// If AWS async caching is not enabled, use the old code path // 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) cr.UpdateCacheFn(ctx, resp)
} else { } else {
// time how long shouldCacheQuery takes // time how long shouldCacheQuery takes

@ -50,7 +50,7 @@ func (m *LoggerMiddleware) logRequest(ctx context.Context, fn func(ctx context.C
if err != nil { if err != nil {
logParams = append(logParams, "error", err) 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)) 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 { for refID, dr := range resp.Responses {
if dr.Error != nil { if dr.Error != nil {
logParams := []any{"refID", refID, "status", int(dr.Status), "error", dr.Error} 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)) logParams = append(logParams, "statusSource", pluginrequestmeta.StatusSourceFromPluginErrorSource(dr.ErrorSource))
} }
ctxLogger.Error("Partial data response error", logParams...) ctxLogger.Error("Partial data response error", logParams...)

@ -33,7 +33,7 @@ type MetricsMiddleware struct {
func newMetricsMiddleware(promRegisterer prometheus.Registerer, pluginRegistry registry.Service, features featuremgmt.FeatureToggles) *MetricsMiddleware { func newMetricsMiddleware(promRegisterer prometheus.Registerer, pluginRegistry registry.Service, features featuremgmt.FeatureToggles) *MetricsMiddleware {
var additionalLabels []string var additionalLabels []string
if features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { if features.IsEnabledGlobally(featuremgmt.FlagPluginsInstrumentationStatusSource) {
additionalLabels = []string{"status_source"} additionalLabels = []string{"status_source"}
} }
pluginRequestCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ pluginRequestCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
@ -122,7 +122,7 @@ func (m *MetricsMiddleware) instrumentPluginRequest(ctx context.Context, pluginC
pluginRequestDurationLabels := []string{pluginCtx.PluginID, endpoint, target} pluginRequestDurationLabels := []string{pluginCtx.PluginID, endpoint, target}
pluginRequestCounterLabels := []string{pluginCtx.PluginID, endpoint, status.String(), target} pluginRequestCounterLabels := []string{pluginCtx.PluginID, endpoint, status.String(), target}
pluginRequestDurationSecondsLabels := []string{"grafana-backend", 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) statusSource := pluginrequestmeta.StatusSourceFromContext(ctx)
pluginRequestDurationLabels = append(pluginRequestDurationLabels, string(statusSource)) pluginRequestDurationLabels = append(pluginRequestDurationLabels, string(statusSource))
pluginRequestCounterLabels = append(pluginRequestCounterLabels, string(statusSource)) pluginRequestCounterLabels = append(pluginRequestCounterLabels, string(statusSource))

@ -175,7 +175,7 @@ func NewAsExternalStep(cfg *config.Cfg) *AsExternal {
// Filter will filter out any plugins that are marked to be disabled. // 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) { 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 return bundles, nil
} }

@ -75,7 +75,7 @@ func TestIntegrationPluginManager(t *testing.T) {
Azure: &azsettings.AzureSettings{}, Azure: &azsettings.AzureSettings{},
// nolint:staticcheck // nolint:staticcheck
IsFeatureToggleEnabled: features.IsEnabled, IsFeatureToggleEnabled: features.IsEnabledGlobally,
} }
tracer := tracing.InitializeTracerForTest() tracer := tracing.InitializeTracerForTest()

@ -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 { 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 var middlewares []plugins.ClientMiddleware
if features.IsEnabled(featuremgmt.FlagPluginsInstrumentationStatusSource) { if features.IsEnabledGlobally(featuremgmt.FlagPluginsInstrumentationStatusSource) {
middlewares = []plugins.ClientMiddleware{ middlewares = []plugins.ClientMiddleware{
clientmiddleware.NewPluginRequestMetaMiddleware(), 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 // 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)) middlewares = append(middlewares, clientmiddleware.NewCachingMiddlewareWithFeatureManager(cachingService, features))
} }
if features.IsEnabled(featuremgmt.FlagIdForwarding) { if features.IsEnabledGlobally(featuremgmt.FlagIdForwarding) {
middlewares = append(middlewares, clientmiddleware.NewForwardIDMiddleware()) middlewares = append(middlewares, clientmiddleware.NewForwardIDMiddleware())
} }
@ -186,7 +186,7 @@ func CreateMiddlewares(cfg *setting.Cfg, oAuthTokenService oauthtoken.OAuthToken
middlewares = append(middlewares, clientmiddleware.NewHTTPClientMiddleware()) 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 // StatusSourceMiddleware should be at the very bottom, or any middlewares below it won't see the
// correct status source in their context.Context // correct status source in their context.Context
middlewares = append(middlewares, clientmiddleware.NewStatusSourceMiddleware()) middlewares = append(middlewares, clientmiddleware.NewStatusSourceMiddleware())

@ -23,7 +23,7 @@ type Service struct {
func ProvideService(cfg *config.Cfg, reg extsvcauth.ExternalServiceRegistry, settingsSvc pluginsettings.Service) *Service { func ProvideService(cfg *config.Cfg, reg extsvcauth.ExternalServiceRegistry, settingsSvc pluginsettings.Service) *Service {
s := &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"), log: log.New("plugins.external.registration"),
reg: reg, reg: reg,
settingsSvc: settingsSvc, settingsSvc: settingsSvc,

@ -1,9 +1,11 @@
package api package api
import ( import (
"context"
"net/http" "net/http"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
@ -41,7 +43,7 @@ func ProvideApi(
} }
// attach api if PublicDashboards feature flag is enabled // attach api if PublicDashboards feature flag is enabled
if features.IsEnabled(featuremgmt.FlagPublicDashboards) { if features.IsEnabledGlobally(featuremgmt.FlagPublicDashboards) {
api.RegisterAPIEndpoints() api.RegisterAPIEndpoints()
} }
@ -284,9 +286,9 @@ func (api *Api) DeletePublicDashboard(c *contextmodel.ReqContext) response.Respo
} }
// Copied from pkg/api/metrics.go // 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 statusWhenError := http.StatusBadRequest
if features.IsEnabled(featuremgmt.FlagDatasourceQueryMultiStatus) { if features.IsEnabled(ctx, featuremgmt.FlagDatasourceQueryMultiStatus) {
statusWhenError = http.StatusMultiStatus statusWhenError = http.StatusMultiStatus
} }

@ -5,6 +5,7 @@ import (
"strconv" "strconv"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" 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 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 // swagger:route GET /public/dashboards/{accessToken}/annotations dashboard_public getPublicAnnotations

@ -37,7 +37,7 @@ func (rs *RenderingService) GetRenderUser(ctx context.Context, key string) (*Ren
var renderUser *RenderUser var renderUser *RenderUser
if looksLikeJWT(key) && rs.features.IsEnabled(featuremgmt.FlagRenderAuthJWT) { if looksLikeJWT(key) && rs.features.IsEnabled(ctx, featuremgmt.FlagRenderAuthJWT) {
from = "jwt" from = "jwt"
renderUser = rs.getRenderUserFromJWT(key) renderUser = rs.getRenderUserFromJWT(key)
} else { } else {

@ -84,7 +84,7 @@ func ProvideService(cfg *setting.Cfg, features *featuremgmt.FeatureManager, remo
} }
var renderKeyProvider renderKeyProvider var renderKeyProvider renderKeyProvider
if features.IsEnabled(featuremgmt.FlagRenderAuthJWT) { if features.IsEnabledGlobally(featuremgmt.FlagRenderAuthJWT) {
renderKeyProvider = &jwtRenderKeyProvider{ renderKeyProvider = &jwtRenderKeyProvider{
log: logger, log: logger,
authToken: []byte(cfg.RendererAuthToken), authToken: []byte(cfg.RendererAuthToken),

@ -116,7 +116,7 @@ func ProvideService(cfg *setting.Cfg, sql db.DB, entityEventStore store.EntityEv
} }
func (s *StandardSearchService) IsDisabled() bool { 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 { func (s *StandardSearchService) Run(ctx context.Context) error {

@ -44,7 +44,7 @@ func (s *DataSourceSecretMigrationService) Migrate(ctx context.Context) error {
} }
logger.Debug(fmt.Sprint("secret migration status is ", migrationStatus)) 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 // 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 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 // If a complete migration happened and now backwards compatibility is enabled, copy secrets back to legacy
needCompatibility := migrationStatus != compatibleSecretMigrationValue && !disableSecretsCompatibility needCompatibility := migrationStatus != compatibleSecretMigrationValue && !disableSecretsCompatibility

@ -48,7 +48,7 @@ func NewPluginSecretsKVStore(
secretsService: secretsService, secretsService: secretsService,
log: logger, log: logger,
kvstore: kvstore, kvstore: kvstore,
backwardsCompatibilityDisabled: features.IsEnabled(featuremgmt.FlagDisableSecretsCompatibility), backwardsCompatibilityDisabled: features.IsEnabledGlobally(featuremgmt.FlagDisableSecretsCompatibility),
fallbackStore: fallback, fallbackStore: fallback,
} }
} }

@ -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 return f.returnValue
} }

@ -79,7 +79,7 @@ func ProvideSecretsService(
log: log.New("secrets"), log: log.New("secrets"),
} }
enabled := !features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) enabled := !features.IsEnabledGlobally(featuremgmt.FlagDisableEnvelopeEncryption)
if enabled { if enabled {
err := s.InitProviders() err := s.InitProviders()
@ -112,12 +112,12 @@ func (s *SecretsService) InitProviders() (err error) {
} }
func (s *SecretsService) registerUsageMetrics() { 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) usageMetrics := make(map[string]any)
// Enabled / disabled // Enabled / disabled
usageMetrics["stats.encryption.envelope_encryption_enabled.count"] = 0 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 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) { func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) {
// Use legacy encryption service if featuremgmt.FlagDisableEnvelopeEncryption toggle is on // 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) 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 // If encrypted with envelope encryption, the feature is disabled and
// no provider is initialized, then we throw an error. // no provider is initialized, then we throw an error.
if s.encryptedWithEnvelopeEncryption(payload) && if s.encryptedWithEnvelopeEncryption(payload) &&
s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) && s.features.IsEnabled(ctx, featuremgmt.FlagDisableEnvelopeEncryption) &&
!s.providersInitialized() { !s.providersInitialized() {
err = fmt.Errorf("failed to decrypt a secret encrypted with envelope encryption: envelope encryption is disabled") err = fmt.Errorf("failed to decrypt a secret encrypted with envelope encryption: envelope encryption is disabled")
return nil, err return nil, err
@ -469,7 +469,7 @@ func (s *SecretsService) RotateDataKeys(ctx context.Context) error {
func (s *SecretsService) ReEncryptDataKeys(ctx context.Context) error { func (s *SecretsService) ReEncryptDataKeys(ctx context.Context) error {
s.log.Info("Data keys re-encryption triggered") 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...") s.log.Info("Envelope encryption is not enabled but trying to init providers anyway...")
if err := s.InitProviders(); err != nil { if err := s.InitProviders(); err != nil {

@ -114,7 +114,7 @@ func (m *SecretsMigrator) RollBackSecrets(ctx context.Context) (bool, error) {
} }
func (m *SecretsMigrator) initProvidersIfNeeded() 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...") logger.Info("Envelope encryption is not enabled but trying to init providers anyway...")
if err := m.secretsSrv.InitProviders(); err != nil { if err := m.secretsSrv.InitProviders(); err != nil {

@ -48,7 +48,7 @@ func NewServiceAccountsAPI(
RouterRegister: routerRegister, RouterRegister: routerRegister,
log: log.New("serviceaccounts.api"), log: log.New("serviceaccounts.api"),
permissionService: permissionService, permissionService: permissionService,
isExternalSAEnabled: features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabled(featuremgmt.FlagExternalServiceAuth), isExternalSAEnabled: features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAuth),
} }
} }

@ -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 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 // Register the metrics
esa.metrics = newMetrics(reg, saSvc, logger) 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. // 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) { 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. // 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") esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services")
return nil, nil 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 { 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. // 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") esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services")
return nil 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 // 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) { 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. // 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") esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services")
return 0, nil return 0, nil
} }

@ -38,7 +38,7 @@ func ProvideServiceAccountsProxy(
s := &ServiceAccountsProxy{ s := &ServiceAccountsProxy{
log: log.New("serviceaccounts.proxy"), log: log.New("serviceaccounts.proxy"),
proxiedService: proxiedService, 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) serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, permissionService, features)

@ -184,7 +184,7 @@ func TestMigrations(t *testing.T) {
{ {
desc: "without editors can admin", desc: "without editors can admin",
// nolint:staticcheck // nolint:staticcheck
config: setting.NewCfgWithFeatures(featuremgmt.WithFeatures("accesscontrol").IsEnabled), config: setting.NewCfgWithFeatures(featuremgmt.WithFeatures("accesscontrol").IsEnabledGlobally),
expectedRolePerms: map[string][]rawPermission{ expectedRolePerms: map[string][]rawPermission{
"managed:users:1:permissions": {{Action: "teams:read", Scope: team1Scope}}, "managed:users:1:permissions": {{Action: "teams:read", Scope: team1Scope}},
"managed:users:2:permissions": {{Action: "teams:read", Scope: team1Scope}}, "managed:users:2:permissions": {{Action: "teams:read", Scope: team1Scope}},

@ -84,7 +84,7 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi
} }
var f PermissionsFilter var f PermissionsFilter
if features.IsEnabled(featuremgmt.FlagPermissionsFilterRemoveSubquery) { if features.IsEnabledGlobally(featuremgmt.FlagPermissionsFilterRemoveSubquery) {
f = &accessControlDashboardPermissionFilterNoFolderSubquery{ f = &accessControlDashboardPermissionFilterNoFolderSubquery{
accessControlDashboardPermissionFilter: accessControlDashboardPermissionFilter{ accessControlDashboardPermissionFilter: accessControlDashboardPermissionFilter{
user: user, folderActions: folderActions, dashboardActions: dashboardActions, features: features, user: user, folderActions: folderActions, dashboardActions: dashboardActions, features: features,
@ -201,7 +201,7 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
} }
permSelector.WriteRune(')') permSelector.WriteRune(')')
switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
case true: case true:
if len(permSelectorArgs) > 0 { if len(permSelectorArgs) > 0 {
switch f.recursiveQueriesAreSupported { switch f.recursiveQueriesAreSupported {
@ -280,7 +280,7 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() {
permSelector.WriteRune(')') permSelector.WriteRune(')')
switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
case true: case true:
if len(permSelectorArgs) > 0 { if len(permSelectorArgs) > 0 {
switch f.recursiveQueriesAreSupported { switch f.recursiveQueriesAreSupported {

@ -116,7 +116,7 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
permSelector.WriteRune(')') permSelector.WriteRune(')')
switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
case true: case true:
if len(permSelectorArgs) > 0 { if len(permSelectorArgs) > 0 {
switch f.recursiveQueriesAreSupported { switch f.recursiveQueriesAreSupported {
@ -193,7 +193,7 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses()
} }
permSelector.WriteRune(')') permSelector.WriteRune(')')
switch f.features.IsEnabled(featuremgmt.FlagNestedFolders) { switch f.features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
case true: case true:
if len(permSelectorArgs) > 0 { if len(permSelectorArgs) > 0 {
switch f.recursiveQueriesAreSupported { switch f.recursiveQueriesAreSupported {

@ -37,7 +37,7 @@ func (b *Builder) ToSQL(limit, page int64) (string, []any) {
INNER JOIN dashboard ON ids.id = dashboard.id`) INNER JOIN dashboard ON ids.id = dashboard.id`)
b.sql.WriteString("\n") b.sql.WriteString("\n")
if b.Features.IsEnabled(featuremgmt.FlagNestedFolders) { if b.Features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
b.sql.WriteString( b.sql.WriteString(
`LEFT OUTER JOIN folder ON folder.uid = dashboard.folder_uid AND folder.org_id = dashboard.org_id`) `LEFT OUTER JOIN folder ON folder.uid = dashboard.folder_uid AND folder.org_id = dashboard.org_id`)
} else { } else {
@ -67,7 +67,7 @@ func (b *Builder) buildSelect() {
dashboard.folder_id, dashboard.folder_id,
folder.uid AS folder_uid, folder.uid AS folder_uid,
`) `)
if b.Features.IsEnabled(featuremgmt.FlagNestedFolders) { if b.Features.IsEnabledGlobally(featuremgmt.FlagNestedFolders) {
b.sql.WriteString(` b.sql.WriteString(`
folder.title AS folder_slug,`) folder.title AS folder_slug,`)
} else { } else {

@ -177,7 +177,7 @@ func makeSQLStoreTestConfig(t *testing.T, tc sqlStoreTest) *setting.Cfg {
tc.features = featuremgmt.WithFeatures() tc.features = featuremgmt.WithFeatures()
} }
// nolint:staticcheck // nolint:staticcheck
cfg := setting.NewCfgWithFeatures(tc.features.IsEnabled) cfg := setting.NewCfgWithFeatures(tc.features.IsEnabledGlobally)
sec, err := cfg.Raw.NewSection("database") sec, err := cfg.Raw.NewSection("database")
require.NoError(t, err) require.NoError(t, err)

@ -45,7 +45,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl,
fbStrategies: strategies, fbStrategies: strategies,
} }
if features.IsEnabled(featuremgmt.FlagSsoSettingsApi) { if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) {
ssoSettingsApi := api.ProvideApi(svc, routeRegister, ac) ssoSettingsApi := api.ProvideApi(svc, routeRegister, ac)
ssoSettingsApi.RegisterAPIEndpoints() ssoSettingsApi.RegisterAPIEndpoints()
} }

@ -14,7 +14,7 @@ import (
func MigrateEntityStore(xdb db.DB, features featuremgmt.FeatureToggles) error { func MigrateEntityStore(xdb db.DB, features featuremgmt.FeatureToggles) error {
// Skip if feature flag is not enabled // Skip if feature flag is not enabled
if !features.IsEnabled(featuremgmt.FlagEntityStore) { if !features.IsEnabledGlobally(featuremgmt.FlagEntityStore) {
return nil return nil
} }
@ -67,6 +67,6 @@ func MigrateEntityStore(xdb db.DB, features featuremgmt.FeatureToggles) error {
} }
return mg.Start( return mg.Start(
features.IsEnabled(featuremgmt.FlagMigrationLocking), features.IsEnabledGlobally(featuremgmt.FlagMigrationLocking),
sql.GetMigrationLockAttemptTimeout()) sql.GetMigrationLockAttemptTimeout())
} }

@ -70,7 +70,7 @@ type EntityEventsService interface {
} }
func ProvideEntityEventsService(cfg *setting.Cfg, sqlStore db.DB, features featuremgmt.FeatureToggles) EntityEventsService { func ProvideEntityEventsService(cfg *setting.Cfg, sqlStore db.DB, features featuremgmt.FeatureToggles) EntityEventsService {
if !features.IsEnabled(featuremgmt.FlagPanelTitleSearch) { if !features.IsEnabledGlobally(featuremgmt.FlagPanelTitleSearch) {
return &dummyEntityEventsService{} return &dummyEntityEventsService{}
} }

@ -212,7 +212,7 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c
QueryString: aws.String(modifiedQueryString), 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 var logGroupIdentifiers []string
for _, lg := range logsQuery.LogGroups { for _, lg := range logsQuery.LogGroups {
arn := lg.Arn arn := lg.Arn

@ -7,8 +7,9 @@ import (
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" "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/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources"
) )
type LogsAPI struct { 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) 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 { type MockLogEvents struct {
cloudwatchlogsiface.CloudWatchLogsAPI cloudwatchlogsiface.CloudWatchLogsAPI

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" "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" "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)) 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 // 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)) mux.HandleFunc("/regions", routes.ResourceRequestMiddleware(routes.RegionsHandler, logger, e.getRequestContext))
} else { } else {
mux.HandleFunc("/regions", handleResourceReq(e.handleGetRegions)) mux.HandleFunc("/regions", handleResourceReq(e.handleGetRegions))

@ -8,17 +8,19 @@ import (
"testing" "testing"
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/mocks"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
) )
func TestLogGroupFieldsRoute(t *testing.T) { 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) { 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) { t.Run("returns 400 if an invalid LogGroupFieldsRequest is used", func(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()

@ -7,6 +7,7 @@ import (
"net/url" "net/url"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" "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 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
} }

@ -8,13 +8,14 @@ import (
"testing" "testing"
"github.com/grafana/grafana-plugin-sdk-go/backend" "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/services/featuremgmt"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/resources"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
) )
func TestLogGroupsRoute(t *testing.T) { func TestLogGroupsRoute(t *testing.T) {
@ -23,10 +24,9 @@ func TestLogGroupsRoute(t *testing.T) {
newLogGroupsService = origLogGroupsService newLogGroupsService = origLogGroupsService
}) })
mockFeatures := mocks.MockFeatures{} mockFeatures := featuremgmt.WithFeatures()
mockFeatures.On("IsEnabled", featuremgmt.FlagCloudWatchCrossAccountQuerying).Return(false)
reqCtxFunc := func(_ context.Context, pluginCtx backend.PluginContext, region string) (reqCtx models.RequestContext, err error) { 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) { t.Run("successfully returns 1 log group with account id", func(t *testing.T) {

@ -37,7 +37,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger
} }
requestQueries, err := models.ParseMetricDataQueries(req.Queries, startTime, endTime, instance.Settings.Region, 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 { if err != nil {
return nil, err return nil, err
} }
@ -60,7 +60,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger
region := r region := r
batches := [][]*models.CloudWatchQuery{regionQueries} batches := [][]*models.CloudWatchQuery{regionQueries}
if e.features.IsEnabled(featuremgmt.FlagCloudWatchBatchQueries) { if e.features.IsEnabled(ctx, featuremgmt.FlagCloudWatchBatchQueries) {
batches = getMetricQueryBatches(regionQueries, logger) batches = getMetricQueryBatches(regionQueries, logger)
} }
@ -95,7 +95,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger
return err 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) requestQueries, err = e.getDimensionValuesForWildcards(ctx, req.PluginContext, region, client, requestQueries, instance.tagValueCache, logger)
if err != nil { if err != nil {
return err return err

@ -173,11 +173,11 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
} }
responseOpts := ResponseOpts{ responseOpts := ResponseOpts{
metricDataplane: s.features.IsEnabled(featuremgmt.FlagLokiMetricDataplane), metricDataplane: s.features.IsEnabled(ctx, featuremgmt.FlagLokiMetricDataplane),
logsDataplane: s.features.IsEnabled(featuremgmt.FlagLokiLogsDataplane), 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) { func queryData(ctx context.Context, req *backend.QueryDataRequest, dsInfo *datasourceInfo, responseOpts ResponseOpts, tracer tracing.Tracer, plog log.Logger, runInParallel bool) (*backend.QueryDataResponse, error) {

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/intervalv2"
"github.com/grafana/grafana/pkg/tsdb/prometheus/client" "github.com/grafana/grafana/pkg/tsdb/prometheus/client"
@ -73,7 +74,7 @@ func New(
// standard deviation sampler is the default for backwards compatibility // standard deviation sampler is the default for backwards compatibility
exemplarSampler := exemplar.NewStandardDeviationSampler exemplarSampler := exemplar.NewStandardDeviationSampler
if features.IsEnabled(featuremgmt.FlagDisablePrometheusExemplarSampling) { if features.IsEnabledGlobally(featuremgmt.FlagDisablePrometheusExemplarSampling) {
exemplarSampler = exemplar.NewNoOpSampler exemplarSampler = exemplar.NewNoOpSampler
} }
@ -85,7 +86,7 @@ func New(
TimeInterval: timeInterval, TimeInterval: timeInterval,
ID: settings.ID, ID: settings.ID,
URL: settings.URL, URL: settings.URL,
enableDataplane: features.IsEnabled(featuremgmt.FlagPrometheusDataplane), enableDataplane: features.IsEnabledGlobally(featuremgmt.FlagPrometheusDataplane),
exemplarSampler: exemplarSampler, exemplarSampler: exemplarSampler,
}, nil }, nil
} }

@ -15,6 +15,7 @@ import (
p "github.com/prometheus/common/model" p "github.com/prometheus/common/model"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery"
"github.com/grafana/kindsys" "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/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/prometheus/client" "github.com/grafana/grafana/pkg/tsdb/prometheus/client"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models" "github.com/grafana/grafana/pkg/tsdb/prometheus/models"
@ -440,8 +442,7 @@ func setup() (*testContext, error) {
JSONData: json.RawMessage(`{"timeInterval": "15s"}`), 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()) opts, err := client.CreateTransportOptions(context.Background(), settings, &setting.Cfg{}, log.New())
if err != nil { if err != nil {
return nil, err return nil, err
@ -460,14 +461,6 @@ func setup() (*testContext, error) {
}, nil }, nil
} }
type fakeFeatureToggles struct {
flags map[string]bool
}
func (f *fakeFeatureToggles) IsEnabled(feature string) bool {
return f.flags[feature]
}
type fakeHttpClientProvider struct { type fakeHttpClientProvider struct {
httpclient.Provider httpclient.Provider
opts httpclient.Options opts httpclient.Options

Loading…
Cancel
Save