K8s/FeatureFlags: Add an apiserver to manage feature flags (dev only) (#80501)

* add deployment registry API cloud only

* update versions

* add feature flag endpoints

* use helpers

* merge main

* update AllowSelfServie and re-run code gen

* fix package name

* add allowselfserve flag to payload

* remove config

* update list api to return the full registry including states

* change enabled check

* fix compile error

* add feature toggle and split path in frontend

* changes

* with status

* add more status/state

* add back config thing

* add back config thing

* merge main

* merge main

* now on the /current api endpoint

* now on the /current api endpoint

* drop frontend changes

* change group name to featuretoggle (singular)

* use the same settings

* now with patch

* more common refs

* more common refs

* WIP actually do the webhook

* fix comment

* fewer imports

* registe standalone

* one less file

* fix singular name

---------

Co-authored-by: Michael Mandrus <michael.mandrus@grafana.com>
This commit is contained in:
Ryan McKinley
2024-01-17 21:32:44 -08:00
committed by GitHub
co-authored by Michael Mandrus
parent cbc84a802d
commit 41e523bde7
25 changed files with 4058 additions and 120 deletions
+7 -35
View File
@@ -18,22 +18,20 @@ import (
)
func (hs *HTTPServer) GetFeatureToggles(ctx *contextmodel.ReqContext) response.Response {
cfg := hs.Cfg.FeatureManagement
enabledFeatures := hs.Features.GetEnabled(ctx.Req.Context())
// object being returned
dtos := make([]featuremgmt.FeatureToggleDTO, 0)
// loop through features an add features that should be visible to dtos
for _, ft := range hs.featureManager.GetFlags() {
if isFeatureHidden(ft, cfg.HiddenToggles) {
flag := ft.Name
if hs.featureManager.IsHiddenFromAdminPage(flag, false) {
continue
}
dto := featuremgmt.FeatureToggleDTO{
Name: ft.Name,
Name: flag,
Description: ft.Description,
Enabled: enabledFeatures[ft.Name],
ReadOnly: !isFeatureWriteable(ft, cfg.ReadOnlyToggles) || !isFeatureEditingAllowed(*hs.Cfg),
Enabled: hs.featureManager.IsEnabled(ctx.Req.Context(), flag),
ReadOnly: !hs.featureManager.IsEditableFromAdminPage(flag),
}
dtos = append(dtos, dto)
@@ -46,7 +44,7 @@ func (hs *HTTPServer) GetFeatureToggles(ctx *contextmodel.ReqContext) response.R
}
func (hs *HTTPServer) UpdateFeatureToggle(ctx *contextmodel.ReqContext) response.Response {
featureMgmtCfg := hs.Cfg.FeatureManagement
featureMgmtCfg := hs.featureManager.Settings
if !featureMgmtCfg.AllowEditing {
return response.Error(http.StatusForbidden, "feature toggles are read-only", fmt.Errorf("feature toggles are configured to be read-only"))
}
@@ -67,7 +65,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.featureManager.LookupFlag(t.Name); ok && isFeatureWriteable(f, hs.Cfg.FeatureManagement.ReadOnlyToggles) {
if hs.featureManager.IsEditableFromAdminPage(t.Name) {
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 {
@@ -92,32 +90,6 @@ func (hs *HTTPServer) GetFeatureMgmtState(ctx *contextmodel.ReqContext) response
return response.Respond(http.StatusOK, fmState)
}
// 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 || flag.HideFromAdminPage
}
// 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) && flag.AllowSelfServe
}
// 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.UpdateWebhook != ""
}
type UpdatePayload struct {
FeatureToggles map[string]string `json:"feature_toggles"`
User string `json:"user"`
+2 -5
View File
@@ -393,9 +393,8 @@ func runGetScenario(
) []featuremgmt.FeatureToggleDTO {
// Set up server and send request
cfg := setting.NewCfg()
cfg.FeatureManagement = settings
fm := featuremgmt.WithFeatureManager(append([]*featuremgmt.FeatureFlag{{
fm := featuremgmt.WithFeatureManager(settings, append([]*featuremgmt.FeatureFlag{{
Name: featuremgmt.FlagFeatureToggleAdminPage,
Stage: featuremgmt.FeatureStageGeneralAvailability,
}}, features...), disabled...)
@@ -460,9 +459,7 @@ func runSetScenario(
) *http.Response {
// Set up server and send request
cfg := setting.NewCfg()
cfg.FeatureManagement = settings
features := featuremgmt.WithFeatureManager(append([]*featuremgmt.FeatureFlag{{
features := featuremgmt.WithFeatureManager(settings, append([]*featuremgmt.FeatureFlag{{
Name: featuremgmt.FlagFeatureToggleAdminPage,
Stage: featuremgmt.FeatureStageGeneralAvailability,
}}, serverFeatures...), disabled...)