grafana/pkg/api/featuremgmt.go

96 lines
3.9 KiB
Go
Raw Normal View History

package api
import (
2023-08-09 10:32:28 -05:00
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/api/response"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2023-08-09 10:32:28 -05:00
"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 {
2023-08-09 10:32:28 -05:00
cfg := hs.Cfg.FeatureManagement
enabledFeatures := hs.Features.GetEnabled(ctx.Req.Context())
2023-08-09 10:32:28 -05:00
// 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
}
2023-08-09 10:32:28 -05:00
dto := featuremgmt.FeatureToggleDTO{
Name: ft.Name,
Description: ft.Description,
Enabled: enabledFeatures[ft.Name],
ReadOnly: !isFeatureWriteable(ft, cfg.ReadOnlyToggles) || !isFeatureEditingAllowed(*hs.Cfg),
}
dtos = append(dtos, dto)
}
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))
}
}
2023-08-09 10:32:28 -05:00
// 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 != ""
}