FeatureFlags: Use interface rather than manager (#80000)

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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