Feature toggles management: Define get feature toggles api (#72106)

* Feature Toggle Management: Define get feature toggles api

* lint
This commit is contained in:
João Calisto
2023-07-24 16:12:59 -04:00
committed by GitHub
parent 425c92a92b
commit 4ba83173ea
11 changed files with 206 additions and 2 deletions
+14 -1
View File
@@ -421,6 +421,19 @@ func (hs *HTTPServer) declareFixedRoles() error {
Grants: []string{"Admin"},
}
featuremgmtReaderRole := ac.RoleRegistration{
Role: ac.RoleDTO{
Name: "fixed:featuremgmt:reader",
DisplayName: "Feature Management reader",
Description: "Read feature toggles",
Group: "Feature Management",
Permissions: []ac.Permission{
{Action: ac.ActionFeatureManagementRead},
},
},
Grants: []string{"Admin"},
}
return hs.accesscontrolService.DeclareFixedRoles(
provisioningWriterRole, datasourcesReaderRole, builtInDatasourceReader, datasourcesWriterRole,
datasourcesIdReaderRole, orgReaderRole, orgWriterRole,
@@ -428,7 +441,7 @@ func (hs *HTTPServer) declareFixedRoles() error {
annotationsReaderRole, dashboardAnnotationsWriterRole, annotationsWriterRole,
dashboardsCreatorRole, dashboardsReaderRole, dashboardsWriterRole,
foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyReaderRole, apikeyWriterRole,
publicDashboardsWriterRole,
publicDashboardsWriterRole, featuremgmtReaderRole,
)
}
+6
View File
@@ -421,6 +421,12 @@ func (hs *HTTPServer) registerRoutes() {
pluginRoute.Get("/:pluginId/metrics", reqOrgAdmin, routing.Wrap(hs.CollectPluginMetrics))
})
if hs.Features.IsEnabled(featuremgmt.FlagFeatureToggleAdminPage) {
apiRoute.Group("/featuremgmt", func(featuremgmtRoute routing.RouteRegister) {
featuremgmtRoute.Get("/", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.GetFeatureToggles)
})
}
apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings)
apiRoute.Any("/datasources/proxy/:id/*", authorize(ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/proxy/uid/:uid/*", authorize(ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequestWithUID)
+30
View File
@@ -0,0 +1,30 @@
package api
import (
"net/http"
"github.com/grafana/grafana/pkg/api/response"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
)
func (hs *HTTPServer) GetFeatureToggles(ctx *contextmodel.ReqContext) response.Response {
featureMgmtCfg := hs.Cfg.FeatureManagement
features := hs.Features.GetFlags()
enabledFeatures := hs.Features.GetEnabled(ctx.Req.Context())
for i := 0; i < len(features); {
ft := features[i]
if _, ok := featureMgmtCfg.HiddenToggles[ft.Name]; ok {
features = append(features[:i], features[i+1:]...) // remove feature
continue
}
if _, ok := featureMgmtCfg.ReadOnlyToggles[ft.Name]; ok {
features[i].ReadOnly = true
}
features[i].Enabled = enabledFeatures[ft.Name]
i++
}
return response.JSON(http.StatusOK, features)
}
+97
View File
@@ -0,0 +1,97 @@
package api
import (
"encoding/json"
"testing"
"net/http"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/user"
"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) {
type testCase struct {
desc string
permissions []accesscontrol.Permission
features []interface{}
expectedCode int
hiddenTogles map[string]struct{}
readOnlyToggles map[string]struct{}
}
tests := []testCase{
{
desc: "should not be able to get feature toggles without permissions",
permissions: []accesscontrol.Permission{},
features: []interface{}{},
expectedCode: http.StatusForbidden,
},
{
desc: "should be able to get feature toggles with correct permissions",
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionFeatureManagementRead}},
features: []interface{}{"toggle1", true, "toggle2", false},
expectedCode: http.StatusOK,
},
{
desc: "hidden toggles are not present in the response",
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionFeatureManagementRead}},
features: []interface{}{"toggle1", true, "toggle2", false},
expectedCode: http.StatusOK,
hiddenTogles: map[string]struct{}{"toggle1": {}},
},
{
desc: "read only toggles have the readOnly field set",
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionFeatureManagementRead}},
features: []interface{}{"toggle1", true, "toggle2", false},
expectedCode: http.StatusOK,
hiddenTogles: map[string]struct{}{"toggle1": {}},
readOnlyToggles: map[string]struct{}{"toggle2": {}},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.FeatureManagement.HiddenToggles = tt.hiddenTogles
cfg.FeatureManagement.ReadOnlyToggles = tt.readOnlyToggles
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg
hs.Features = featuremgmt.WithFeatures(append([]interface{}{"featureToggleAdminPage", true}, tt.features...)...)
hs.orgService = orgtest.NewOrgServiceFake()
hs.userService = &usertest.FakeUserService{
ExpectedUser: &user.User{ID: 1},
}
})
req := webtest.RequestWithSignedInUser(server.NewGetRequest("/api/featuremgmt"), userWithPermissions(1, tt.permissions))
res, err := server.SendJSON(req)
require.NoError(t, err)
defer func() { require.NoError(t, res.Body.Close()) }()
assert.Equal(t, tt.expectedCode, res.StatusCode)
if tt.expectedCode == http.StatusOK {
var result []featuremgmt.FeatureFlag
err := json.NewDecoder(res.Body).Decode(&result)
require.NoError(t, err)
for _, ft := range result {
if _, ok := tt.hiddenTogles[ft.Name]; ok {
t.Fail()
}
if _, ok := tt.readOnlyToggles[ft.Name]; ok {
assert.True(t, ft.ReadOnly, tt.desc)
}
}
assert.Equal(t, 3-len(tt.hiddenTogles), len(result), tt.desc)
}
})
}
}