2023-07-24 15:12:59 -05:00
package api
import (
2023-09-25 13:11:24 -05:00
"bytes"
"encoding/json"
2023-08-09 10:32:28 -05:00
"fmt"
2023-09-25 13:11:24 -05:00
"io"
2023-07-24 15:12:59 -05:00
"net/http"
2023-10-26 04:42:00 -05:00
"sort"
2023-09-25 13:11:24 -05:00
"strconv"
2023-07-24 15:12:59 -05:00
"github.com/grafana/grafana/pkg/api/response"
2023-09-25 13:11:24 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2023-07-24 15:12:59 -05:00
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"
2023-07-24 15:12:59 -05:00
)
func ( hs * HTTPServer ) GetFeatureToggles ( ctx * contextmodel . ReqContext ) response . Response {
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
2024-01-09 12:38:06 -06:00
for _ , ft := range hs . featureManager . GetFlags ( ) {
2024-01-17 23:32:44 -06:00
flag := ft . Name
if hs . featureManager . IsHiddenFromAdminPage ( flag , false ) {
2023-07-24 15:12:59 -05:00
continue
}
2023-08-09 10:32:28 -05:00
dto := featuremgmt . FeatureToggleDTO {
2024-01-17 23:32:44 -06:00
Name : flag ,
2023-08-09 10:32:28 -05:00
Description : ft . Description ,
2024-01-17 23:32:44 -06:00
Enabled : hs . featureManager . IsEnabled ( ctx . Req . Context ( ) , flag ) ,
ReadOnly : ! hs . featureManager . IsEditableFromAdminPage ( flag ) ,
2023-08-09 10:32:28 -05:00
}
dtos = append ( dtos , dto )
2023-10-26 04:42:00 -05:00
sort . Slice ( dtos , func ( i , j int ) bool {
return dtos [ i ] . Name < dtos [ j ] . Name
} )
2023-08-09 10:32:28 -05:00
}
return response . JSON ( http . StatusOK , dtos )
}
func ( hs * HTTPServer ) UpdateFeatureToggle ( ctx * contextmodel . ReqContext ) response . Response {
2024-01-17 23:32:44 -06:00
featureMgmtCfg := hs . featureManager . Settings
2023-08-09 10:32:28 -05:00
if ! featureMgmtCfg . AllowEditing {
return response . Error ( http . StatusForbidden , "feature toggles are read-only" , fmt . Errorf ( "feature toggles are configured to be read-only" ) )
}
2023-09-25 13:11:24 -05:00
if featureMgmtCfg . UpdateWebhook == "" {
return response . Error ( http . StatusInternalServerError , "feature toggles service is misconfigured" , fmt . Errorf ( "[feature_management]update_webhook is not set" ) )
2023-08-09 10:32:28 -05:00
}
cmd := featuremgmt . UpdateFeatureTogglesCommand { }
if err := web . Bind ( ctx . Req , & cmd ) ; err != nil {
return response . Error ( http . StatusBadRequest , "bad request data" , err )
}
2023-09-25 13:11:24 -05:00
payload := UpdatePayload {
FeatureToggles : make ( map [ string ] string , len ( cmd . FeatureToggles ) ) ,
User : ctx . SignedInUser . Email ,
}
2023-08-09 10:32:28 -05:00
for _ , t := range cmd . FeatureToggles {
// make sure flag exists, and only continue if flag is writeable
2024-01-17 23:32:44 -06:00
if hs . featureManager . IsEditableFromAdminPage ( t . Name ) {
2023-08-09 10:32:28 -05:00
hs . log . Info ( "UpdateFeatureToggle: updating toggle" , "toggle_name" , t . Name , "enabled" , t . Enabled , "username" , ctx . SignedInUser . Login )
2023-09-25 13:11:24 -05:00
payload . FeatureToggles [ t . Name ] = strconv . FormatBool ( t . Enabled )
2023-08-09 10:32:28 -05:00
} 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-07-24 15:12:59 -05:00
}
}
2023-09-25 13:11:24 -05:00
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" )
}
2023-08-09 10:32:28 -05:00
2024-01-09 12:38:06 -06:00
hs . featureManager . SetRestartRequired ( )
2023-10-13 05:54:34 -05:00
2023-09-25 13:11:24 -05:00
return response . Respond ( http . StatusOK , "feature toggles updated successfully" )
2023-08-09 10:32:28 -05:00
}
2023-10-13 05:54:34 -05:00
func ( hs * HTTPServer ) GetFeatureMgmtState ( ctx * contextmodel . ReqContext ) response . Response {
2024-01-09 12:38:06 -06:00
fmState := hs . featureManager . GetState ( )
2023-10-13 05:54:34 -05:00
return response . Respond ( http . StatusOK , fmState )
}
2023-09-25 13:11:24 -05:00
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
2023-07-24 15:12:59 -05:00
}