mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
41e523bde7
* 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>
133 lines
4.2 KiB
Go
133 lines
4.2 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
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 {
|
|
// 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() {
|
|
flag := ft.Name
|
|
if hs.featureManager.IsHiddenFromAdminPage(flag, false) {
|
|
continue
|
|
}
|
|
dto := featuremgmt.FeatureToggleDTO{
|
|
Name: flag,
|
|
Description: ft.Description,
|
|
Enabled: hs.featureManager.IsEnabled(ctx.Req.Context(), flag),
|
|
ReadOnly: !hs.featureManager.IsEditableFromAdminPage(flag),
|
|
}
|
|
|
|
dtos = append(dtos, dto)
|
|
sort.Slice(dtos, func(i, j int) bool {
|
|
return dtos[i].Name < dtos[j].Name
|
|
})
|
|
}
|
|
|
|
return response.JSON(http.StatusOK, dtos)
|
|
}
|
|
|
|
func (hs *HTTPServer) UpdateFeatureToggle(ctx *contextmodel.ReqContext) response.Response {
|
|
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"))
|
|
}
|
|
|
|
if featureMgmtCfg.UpdateWebhook == "" {
|
|
return response.Error(http.StatusInternalServerError, "feature toggles service is misconfigured", fmt.Errorf("[feature_management]update_webhook is not set"))
|
|
}
|
|
|
|
cmd := featuremgmt.UpdateFeatureTogglesCommand{}
|
|
if err := web.Bind(ctx.Req, &cmd); err != nil {
|
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
|
}
|
|
|
|
payload := UpdatePayload{
|
|
FeatureToggles: make(map[string]string, len(cmd.FeatureToggles)),
|
|
User: ctx.SignedInUser.Email,
|
|
}
|
|
|
|
for _, t := range cmd.FeatureToggles {
|
|
// make sure flag exists, and only continue if flag is writeable
|
|
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 {
|
|
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))
|
|
}
|
|
}
|
|
|
|
err := sendWebhookUpdate(featureMgmtCfg, payload, hs.log)
|
|
if err != nil {
|
|
hs.log.Error("UpdateFeatureToggle: Failed to perform webhook request", "error", err)
|
|
return response.Respond(http.StatusBadRequest, "Failed to perform webhook request")
|
|
}
|
|
|
|
hs.featureManager.SetRestartRequired()
|
|
|
|
return response.Respond(http.StatusOK, "feature toggles updated successfully")
|
|
}
|
|
|
|
func (hs *HTTPServer) GetFeatureMgmtState(ctx *contextmodel.ReqContext) response.Response {
|
|
fmState := hs.featureManager.GetState()
|
|
return response.Respond(http.StatusOK, fmState)
|
|
}
|
|
|
|
type UpdatePayload struct {
|
|
FeatureToggles map[string]string `json:"feature_toggles"`
|
|
User string `json:"user"`
|
|
}
|
|
|
|
func sendWebhookUpdate(cfg setting.FeatureMgmtSettings, payload UpdatePayload, logger log.Logger) error {
|
|
data, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, cfg.UpdateWebhook, bytes.NewBuffer(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+cfg.UpdateWebhookToken)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
logger.Warn("Failed to close response body", "err", err)
|
|
}
|
|
}()
|
|
|
|
if resp.StatusCode >= http.StatusBadRequest {
|
|
if body, err := io.ReadAll(resp.Body); err != nil {
|
|
return fmt.Errorf("SendWebhookUpdate failed with status=%d, error: %s", resp.StatusCode, string(body))
|
|
} else {
|
|
return fmt.Errorf("SendWebhookUpdate failed with status=%d, error: %w", resp.StatusCode, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|