Feature Toggles: Create API for updating feature toggle state from the feature toggle admin page (#73022)
* create roles for writing feature toggles * create update endpoint / handler * api changes * add feature toggle validations * hide toggles based on their state * make FlagFeatureToggle read only * add username log * add username string * refactor for better readability * refactor unit tests so we can do more validations * some skeletoning for the set tests * write unit tests for updater * break helper functions out * update sample ini to match defaults * add more logic to ReadOnly label * add user documentation * fix lint issue * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: J Stickler <julie.stickler@grafana.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: J Stickler <julie.stickler@grafana.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: J Stickler <julie.stickler@grafana.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: J Stickler <julie.stickler@grafana.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: J Stickler <julie.stickler@grafana.com> * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: J Stickler <julie.stickler@grafana.com> --------- Co-authored-by: IbrahimCSAE <ibrahim.mdev@gmail.com> Co-authored-by: J Stickler <julie.stickler@grafana.com>
This commit is contained in:
co-authored by
J Stickler
IbrahimCSAE
parent
d9695eb507
commit
779e0fe311
+77
-12
@@ -1,30 +1,95 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func (hs *HTTPServer) GetFeatureToggles(ctx *contextmodel.ReqContext) response.Response {
|
||||
featureMgmtCfg := hs.Cfg.FeatureManagement
|
||||
|
||||
features := hs.Features.GetFlags()
|
||||
cfg := hs.Cfg.FeatureManagement
|
||||
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
|
||||
// object being returned
|
||||
dtos := make([]featuremgmt.FeatureToggleDTO, 0)
|
||||
|
||||
// loop through features an add features that should be visible to dtos
|
||||
for _, ft := range hs.Features.GetFlags() {
|
||||
if isFeatureHidden(ft, cfg.HiddenToggles) {
|
||||
continue
|
||||
}
|
||||
if _, ok := featureMgmtCfg.ReadOnlyToggles[ft.Name]; ok {
|
||||
features[i].ReadOnly = true
|
||||
dto := featuremgmt.FeatureToggleDTO{
|
||||
Name: ft.Name,
|
||||
Description: ft.Description,
|
||||
Enabled: enabledFeatures[ft.Name],
|
||||
ReadOnly: !isFeatureWriteable(ft, cfg.ReadOnlyToggles) || !isFeatureEditingAllowed(*hs.Cfg),
|
||||
}
|
||||
features[i].Enabled = enabledFeatures[ft.Name]
|
||||
i++
|
||||
|
||||
dtos = append(dtos, dto)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, features)
|
||||
return response.JSON(http.StatusOK, dtos)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) UpdateFeatureToggle(ctx *contextmodel.ReqContext) response.Response {
|
||||
featureMgmtCfg := hs.Cfg.FeatureManagement
|
||||
if !featureMgmtCfg.AllowEditing {
|
||||
return response.Error(http.StatusForbidden, "feature toggles are read-only", fmt.Errorf("feature toggles are configured to be read-only"))
|
||||
}
|
||||
|
||||
if featureMgmtCfg.UpdateControllerUrl == "" {
|
||||
return response.Error(http.StatusInternalServerError, "feature toggles service is misconfigured", fmt.Errorf("[feature_management]update_controller_url is not set"))
|
||||
}
|
||||
|
||||
cmd := featuremgmt.UpdateFeatureTogglesCommand{}
|
||||
if err := web.Bind(ctx.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
|
||||
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) {
|
||||
hs.log.Info("UpdateFeatureToggle: updating toggle", "toggle_name", t.Name, "enabled", t.Enabled, "username", ctx.SignedInUser.Login)
|
||||
// TODO build payload
|
||||
} else {
|
||||
hs.log.Warn("UpdateFeatureToggle: invalid toggle passed in", "toggle_name", t.Name)
|
||||
return response.Error(http.StatusBadRequest, "invalid toggle passed in", fmt.Errorf("invalid toggle passed in: %s", t.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: post to featureMgmtCfg.UpdateControllerUrl and return response status
|
||||
hs.log.Warn("UpdateFeatureToggle: function is unimplemented")
|
||||
|
||||
return response.Error(http.StatusNotImplemented, "UpdateFeatureToggle is unimplemented", fmt.Errorf("UpdateFeatureToggle is unimplemented"))
|
||||
}
|
||||
|
||||
// isFeatureHidden returns whether a toggle should be hidden from the admin page.
|
||||
// filters out statuses Unknown, Experimental, and Private Preview
|
||||
func isFeatureHidden(flag featuremgmt.FeatureFlag, hideCfg map[string]struct{}) bool {
|
||||
if _, ok := hideCfg[flag.Name]; ok {
|
||||
return true
|
||||
}
|
||||
return flag.Stage == featuremgmt.FeatureStageUnknown || flag.Stage == featuremgmt.FeatureStageExperimental || flag.Stage == featuremgmt.FeatureStagePrivatePreview
|
||||
}
|
||||
|
||||
// isFeatureWriteable returns whether a toggle on the admin page can be updated by the user.
|
||||
// only allows writing of GA and Deprecated toggles, and excludes the feature toggle admin page toggle
|
||||
func isFeatureWriteable(flag featuremgmt.FeatureFlag, readOnlyCfg map[string]struct{}) bool {
|
||||
if _, ok := readOnlyCfg[flag.Name]; ok {
|
||||
return false
|
||||
}
|
||||
if flag.Name == featuremgmt.FlagFeatureToggleAdminPage {
|
||||
return false
|
||||
}
|
||||
return flag.Stage == featuremgmt.FeatureStageGeneralAvailability || flag.Stage == featuremgmt.FeatureStageDeprecated
|
||||
}
|
||||
|
||||
// isFeatureEditingAllowed checks if the backend is properly configured to allow feature toggle changes from the UI
|
||||
func isFeatureEditingAllowed(cfg setting.Cfg) bool {
|
||||
return cfg.FeatureManagement.AllowEditing && cfg.FeatureManagement.UpdateControllerUrl != ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user