Alerting/consistent api errors (#34858)

* consolidates alertmanager api errors

* util & testing consistent errors

* consistent errors for rest of ngalert apis

* updates expected errors in testware

* bump ci

* linting

* unrelated: dashboard.go lint
This commit is contained in:
Owen Diehl
2021-05-28 11:55:03 -04:00
committed by GitHub
parent 179bfecb3e
commit 9aca032d10
13 changed files with 127 additions and 129 deletions

View File

@@ -51,8 +51,7 @@ func (hs *HTTPServer) TrimDashboard(c *models.ReqContext, cmd models.TrimDashboa
dash := cmd.Dashboard dash := cmd.Dashboard
meta := cmd.Meta meta := cmd.Meta
trimedResult := *dash trimedResult, err := hs.LoadSchemaService.DashboardTrimDefaults(*dash)
trimedResult, err = hs.LoadSchemaService.DashboardTrimDefaults(*dash)
if err != nil { if err != nil {
return response.Error(500, "Error while trim default value from dashboard json", err) return response.Error(500, "Error while trim default value from dashboard json", err)
} }

View File

@@ -23,38 +23,38 @@ type AlertmanagerSrv struct {
func (srv AlertmanagerSrv) RouteCreateSilence(c *models.ReqContext, postableSilence apimodels.PostableSilence) response.Response { func (srv AlertmanagerSrv) RouteCreateSilence(c *models.ReqContext, postableSilence apimodels.PostableSilence) response.Response {
if !c.HasUserRole(models.ROLE_EDITOR) { if !c.HasUserRole(models.ROLE_EDITOR) {
return response.Error(http.StatusForbidden, "Permission denied", nil) return ErrResp(http.StatusForbidden, errors.New("permission denied"), "")
} }
silenceID, err := srv.am.CreateSilence(&postableSilence) silenceID, err := srv.am.CreateSilence(&postableSilence)
if err != nil { if err != nil {
if errors.Is(err, notifier.ErrSilenceNotFound) { if errors.Is(err, notifier.ErrSilenceNotFound) {
return response.Error(http.StatusNotFound, err.Error(), nil) return ErrResp(http.StatusNotFound, err, "")
} }
if errors.Is(err, notifier.ErrCreateSilenceBadPayload) { if errors.Is(err, notifier.ErrCreateSilenceBadPayload) {
return response.Error(http.StatusBadRequest, err.Error(), nil) return ErrResp(http.StatusBadRequest, err, "")
} }
return response.Error(http.StatusInternalServerError, "failed to create silence", err) return ErrResp(http.StatusInternalServerError, err, "failed to create silence")
} }
return response.JSON(http.StatusAccepted, util.DynMap{"message": "silence created", "id": silenceID}) return response.JSON(http.StatusAccepted, util.DynMap{"message": "silence created", "id": silenceID})
} }
func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *models.ReqContext) response.Response { func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *models.ReqContext) response.Response {
// not implemented // not implemented
return response.Error(http.StatusNotImplemented, "", nil) return NotImplementedResp
} }
func (srv AlertmanagerSrv) RouteDeleteSilence(c *models.ReqContext) response.Response { func (srv AlertmanagerSrv) RouteDeleteSilence(c *models.ReqContext) response.Response {
if !c.HasUserRole(models.ROLE_EDITOR) { if !c.HasUserRole(models.ROLE_EDITOR) {
return response.Error(http.StatusForbidden, "Permission denied", nil) return ErrResp(http.StatusForbidden, errors.New("permission denied"), "")
} }
silenceID := c.Params(":SilenceId") silenceID := c.Params(":SilenceId")
if err := srv.am.DeleteSilence(silenceID); err != nil { if err := srv.am.DeleteSilence(silenceID); err != nil {
if errors.Is(err, notifier.ErrSilenceNotFound) { if errors.Is(err, notifier.ErrSilenceNotFound) {
return response.Error(http.StatusNotFound, err.Error(), nil) return ErrResp(http.StatusNotFound, err, "")
} }
return response.Error(http.StatusInternalServerError, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"}) return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"})
} }
@@ -63,14 +63,14 @@ func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *models.ReqContext) response
query := ngmodels.GetLatestAlertmanagerConfigurationQuery{} query := ngmodels.GetLatestAlertmanagerConfigurationQuery{}
if err := srv.store.GetLatestAlertmanagerConfiguration(&query); err != nil { if err := srv.store.GetLatestAlertmanagerConfiguration(&query); err != nil {
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) { if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
return response.Error(http.StatusNotFound, err.Error(), nil) return ErrResp(http.StatusNotFound, err, "")
} }
return response.Error(http.StatusInternalServerError, "failed to get latest configuration", err) return ErrResp(http.StatusInternalServerError, err, "failed to get latest configuration")
} }
cfg, err := notifier.Load([]byte(query.Result.AlertmanagerConfiguration)) cfg, err := notifier.Load([]byte(query.Result.AlertmanagerConfiguration))
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to unmarshal alertmanager configuration", err) return ErrResp(http.StatusInternalServerError, err, "failed to unmarshal alertmanager configuration")
} }
result := apimodels.GettableUserConfig{ result := apimodels.GettableUserConfig{
@@ -86,7 +86,7 @@ func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *models.ReqContext) response
for k := range pr.SecureSettings { for k := range pr.SecureSettings {
decryptedValue, err := pr.GetDecryptedSecret(k) decryptedValue, err := pr.GetDecryptedSecret(k)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to decrypt stored secure setting: %s", k), err) return ErrResp(http.StatusInternalServerError, err, "failed to decrypt stored secure setting: %s", k)
} }
if decryptedValue == "" { if decryptedValue == "" {
continue continue
@@ -125,10 +125,10 @@ func (srv AlertmanagerSrv) RouteGetAMAlertGroups(c *models.ReqContext) response.
) )
if err != nil { if err != nil {
if errors.Is(err, notifier.ErrGetAlertGroupsBadPayload) { if errors.Is(err, notifier.ErrGetAlertGroupsBadPayload) {
return response.Error(http.StatusBadRequest, err.Error(), nil) return ErrResp(http.StatusBadRequest, err, "")
} }
// any other error here should be an unexpected failure and thus an internal error // any other error here should be an unexpected failure and thus an internal error
return response.Error(http.StatusInternalServerError, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return response.JSON(http.StatusOK, groups) return response.JSON(http.StatusOK, groups)
@@ -144,10 +144,10 @@ func (srv AlertmanagerSrv) RouteGetAMAlerts(c *models.ReqContext) response.Respo
) )
if err != nil { if err != nil {
if errors.Is(err, notifier.ErrGetAlertsBadPayload) { if errors.Is(err, notifier.ErrGetAlertsBadPayload) {
return response.Error(http.StatusBadRequest, err.Error(), nil) return ErrResp(http.StatusBadRequest, err, "")
} }
// any other error here should be an unexpected failure and thus an internal error // any other error here should be an unexpected failure and thus an internal error
return response.Error(http.StatusInternalServerError, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return response.JSON(http.StatusOK, alerts) return response.JSON(http.StatusOK, alerts)
@@ -158,10 +158,10 @@ func (srv AlertmanagerSrv) RouteGetSilence(c *models.ReqContext) response.Respon
gettableSilence, err := srv.am.GetSilence(silenceID) gettableSilence, err := srv.am.GetSilence(silenceID)
if err != nil { if err != nil {
if errors.Is(err, notifier.ErrSilenceNotFound) { if errors.Is(err, notifier.ErrSilenceNotFound) {
return response.Error(http.StatusNotFound, err.Error(), nil) return ErrResp(http.StatusNotFound, err, "")
} }
// any other error here should be an unexpected failure and thus an internal error // any other error here should be an unexpected failure and thus an internal error
return response.Error(http.StatusInternalServerError, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return response.JSON(http.StatusOK, gettableSilence) return response.JSON(http.StatusOK, gettableSilence)
} }
@@ -170,17 +170,17 @@ func (srv AlertmanagerSrv) RouteGetSilences(c *models.ReqContext) response.Respo
gettableSilences, err := srv.am.ListSilences(c.QueryStrings("filter")) gettableSilences, err := srv.am.ListSilences(c.QueryStrings("filter"))
if err != nil { if err != nil {
if errors.Is(err, notifier.ErrListSilencesBadPayload) { if errors.Is(err, notifier.ErrListSilencesBadPayload) {
return response.Error(http.StatusBadRequest, err.Error(), nil) return ErrResp(http.StatusBadRequest, err, "")
} }
// any other error here should be an unexpected failure and thus an internal error // any other error here should be an unexpected failure and thus an internal error
return response.Error(http.StatusInternalServerError, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return response.JSON(http.StatusOK, gettableSilences) return response.JSON(http.StatusOK, gettableSilences)
} }
func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response { func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
if !c.HasUserRole(models.ROLE_EDITOR) { if !c.HasUserRole(models.ROLE_EDITOR) {
return response.Error(http.StatusForbidden, "Permission denied", nil) return ErrResp(http.StatusForbidden, errors.New("permission denied"), "")
} }
// Get the last known working configuration // Get the last known working configuration
@@ -188,13 +188,13 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body ap
if err := srv.store.GetLatestAlertmanagerConfiguration(&query); err != nil { if err := srv.store.GetLatestAlertmanagerConfiguration(&query); err != nil {
// If we don't have a configuration there's nothing for us to know and we should just continue saving the new one // If we don't have a configuration there's nothing for us to know and we should just continue saving the new one
if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) { if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
return response.Error(http.StatusInternalServerError, "failed to get latest configuration", err) return ErrResp(http.StatusInternalServerError, err, "failed to get latest configuration")
} }
} }
currentConfig, err := notifier.Load([]byte(query.Result.AlertmanagerConfiguration)) currentConfig, err := notifier.Load([]byte(query.Result.AlertmanagerConfiguration))
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to load lastest configuration", err) return ErrResp(http.StatusInternalServerError, err, "failed to load lastest configuration")
} }
currentReceiverMap := currentConfig.GetGrafanaReceiverMap() currentReceiverMap := currentConfig.GetGrafanaReceiverMap()
@@ -208,7 +208,7 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body ap
cgmr, ok := currentReceiverMap[gr.UID] cgmr, ok := currentReceiverMap[gr.UID]
if !ok { if !ok {
// it tries to update a receiver that didn't previously exist // it tries to update a receiver that didn't previously exist
return response.Error(http.StatusBadRequest, fmt.Sprintf("unknown receiver: %s", gr.UID), nil) return ErrResp(http.StatusBadRequest, fmt.Errorf("unknown receiver: %s", gr.UID), "")
} }
// frontend sends only the secure settings that have to be updated // frontend sends only the secure settings that have to be updated
@@ -218,7 +218,7 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body ap
if !ok { if !ok {
decryptedValue, err := cgmr.GetDecryptedSecret(key) decryptedValue, err := cgmr.GetDecryptedSecret(key)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to decrypt stored secure setting: %s", key), err) return ErrResp(http.StatusInternalServerError, err, "failed to decrypt stored secure setting: %s", key)
} }
if body.AlertmanagerConfig.Receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings == nil { if body.AlertmanagerConfig.Receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings == nil {
@@ -232,12 +232,12 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body ap
} }
if err := body.ProcessConfig(); err != nil { if err := body.ProcessConfig(); err != nil {
return response.Error(http.StatusInternalServerError, "failed to post process Alertmanager configuration", err) return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration")
} }
if err := srv.am.SaveAndApplyConfig(&body); err != nil { if err := srv.am.SaveAndApplyConfig(&body); err != nil {
srv.log.Error("unable to save and apply alertmanager configuration", "err", err) srv.log.Error("unable to save and apply alertmanager configuration", "err", err)
return response.Error(http.StatusBadRequest, "failed to save and apply Alertmanager configuration", err) return ErrResp(http.StatusBadRequest, err, "failed to save and apply Alertmanager configuration")
} }
return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration created"}) return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration created"})
@@ -245,5 +245,5 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body ap
func (srv AlertmanagerSrv) RoutePostAMAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response { func (srv AlertmanagerSrv) RoutePostAMAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response {
// not implemented // not implemented
return response.Error(http.StatusNotImplemented, "", nil) return NotImplementedResp
} }

View File

@@ -2,7 +2,6 @@ package api
import ( import (
"errors" "errors"
"fmt"
"net/http" "net/http"
"time" "time"
@@ -38,7 +37,7 @@ func (srv RulerSrv) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) respon
uids, err := srv.store.DeleteNamespaceAlertRules(c.SignedInUser.OrgId, namespace.Uid) uids, err := srv.store.DeleteNamespaceAlertRules(c.SignedInUser.OrgId, namespace.Uid)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to delete namespace alert rules", err) return ErrResp(http.StatusInternalServerError, err, "failed to delete namespace alert rules")
} }
for _, uid := range uids { for _, uid := range uids {
@@ -59,9 +58,9 @@ func (srv RulerSrv) RouteDeleteRuleGroupConfig(c *models.ReqContext) response.Re
if err != nil { if err != nil {
if errors.Is(err, ngmodels.ErrRuleGroupNamespaceNotFound) { if errors.Is(err, ngmodels.ErrRuleGroupNamespaceNotFound) {
return response.Error(http.StatusNotFound, "failed to delete rule group", err) return ErrResp(http.StatusNotFound, err, "failed to delete rule group")
} }
return response.Error(http.StatusInternalServerError, "failed to delete rule group", err) return ErrResp(http.StatusInternalServerError, err, "failed to delete rule group")
} }
for _, uid := range uids { for _, uid := range uids {
@@ -83,7 +82,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
NamespaceUID: namespace.Uid, NamespaceUID: namespace.Uid,
} }
if err := srv.store.GetNamespaceAlertRules(&q); err != nil { if err := srv.store.GetNamespaceAlertRules(&q); err != nil {
return response.Error(http.StatusInternalServerError, "failed to update rule group", err) return ErrResp(http.StatusInternalServerError, err, "failed to update rule group")
} }
result := apimodels.NamespaceConfigResponse{} result := apimodels.NamespaceConfigResponse{}
@@ -126,7 +125,7 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp
RuleGroup: ruleGroup, RuleGroup: ruleGroup,
} }
if err := srv.store.GetRuleGroupAlertRules(&q); err != nil { if err := srv.store.GetRuleGroupAlertRules(&q); err != nil {
return response.Error(http.StatusInternalServerError, "failed to get group alert rules", err) return ErrResp(http.StatusInternalServerError, err, "failed to get group alert rules")
} }
var ruleGroupInterval model.Duration var ruleGroupInterval model.Duration
@@ -151,7 +150,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
OrgID: c.SignedInUser.OrgId, OrgID: c.SignedInUser.OrgId,
} }
if err := srv.store.GetOrgAlertRules(&q); err != nil { if err := srv.store.GetOrgAlertRules(&q); err != nil {
return response.Error(http.StatusInternalServerError, "failed to get alert rules", err) return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
} }
configs := make(map[string]map[string]apimodels.GettableRuleGroupConfig) configs := make(map[string]map[string]apimodels.GettableRuleGroupConfig)
@@ -217,17 +216,17 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
// and rollback the transaction in case of violation // and rollback the transaction in case of violation
limitReached, err := srv.QuotaService.QuotaReached(c, "alert_rule") limitReached, err := srv.QuotaService.QuotaReached(c, "alert_rule")
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to get quota", err) return ErrResp(http.StatusInternalServerError, err, "failed to get quota")
} }
if limitReached { if limitReached {
return response.Error(http.StatusForbidden, "quota reached", nil) return ErrResp(http.StatusForbidden, errors.New("quota reached"), "")
} }
// TODO validate UID uniqueness in the payload // TODO validate UID uniqueness in the payload
//TODO: Should this belong in alerting-api? //TODO: Should this belong in alerting-api?
if ruleGroupConfig.Name == "" { if ruleGroupConfig.Name == "" {
return response.Error(http.StatusBadRequest, "rule group name is not valid", nil) return ErrResp(http.StatusBadRequest, errors.New("rule group name is not valid"), "")
} }
var alertRuleUIDs []string var alertRuleUIDs []string
@@ -238,7 +237,7 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
Data: r.GrafanaManagedAlert.Data, Data: r.GrafanaManagedAlert.Data,
} }
if err := validateCondition(cond, c.SignedInUser, c.SkipCache, srv.DatasourceCache); err != nil { if err := validateCondition(cond, c.SignedInUser, c.SkipCache, srv.DatasourceCache); err != nil {
return response.Error(http.StatusBadRequest, fmt.Sprintf("failed to validate alert rule %s", r.GrafanaManagedAlert.Title), err) return ErrResp(http.StatusBadRequest, err, "failed to validate alert rule %s", r.GrafanaManagedAlert.Title)
} }
alertRuleUIDs = append(alertRuleUIDs, r.GrafanaManagedAlert.UID) alertRuleUIDs = append(alertRuleUIDs, r.GrafanaManagedAlert.UID)
} }
@@ -249,11 +248,11 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
RuleGroupConfig: ruleGroupConfig, RuleGroupConfig: ruleGroupConfig,
}); err != nil { }); err != nil {
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) { if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
return response.Error(http.StatusNotFound, "failed to update rule group", err) return ErrResp(http.StatusNotFound, err, "failed to update rule group")
} else if errors.Is(err, ngmodels.ErrAlertRuleFailedValidation) { } else if errors.Is(err, ngmodels.ErrAlertRuleFailedValidation) {
return response.Error(http.StatusBadRequest, "failed to update rule group", err) return ErrResp(http.StatusBadRequest, err, "failed to update rule group")
} }
return response.Error(http.StatusInternalServerError, "failed to update rule group", err) return ErrResp(http.StatusInternalServerError, err, "failed to update rule group")
} }
for _, uid := range alertRuleUIDs { for _, uid := range alertRuleUIDs {
@@ -292,10 +291,10 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64) apimode
func toNamespaceErrorResponse(err error) response.Response { func toNamespaceErrorResponse(err error) response.Response {
if errors.Is(err, ngmodels.ErrCannotEditNamespace) { if errors.Is(err, ngmodels.ErrCannotEditNamespace) {
return response.Error(http.StatusForbidden, err.Error(), err) return ErrResp(http.StatusForbidden, err, err.Error())
} }
if errors.Is(err, models.ErrDashboardIdentifierNotSet) { if errors.Is(err, models.ErrDashboardIdentifierNotSet) {
return response.Error(http.StatusBadRequest, err.Error(), err) return ErrResp(http.StatusBadRequest, err, err.Error())
} }
return coreapi.ToFolderErrorResponse(err) return coreapi.ToFolderErrorResponse(err)
} }

View File

@@ -1,6 +1,7 @@
package api package api
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@@ -34,20 +35,20 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodel
recipient := c.Params("Recipient") recipient := c.Params("Recipient")
if recipient == apimodels.GrafanaBackend.String() { if recipient == apimodels.GrafanaBackend.String() {
if body.Type() != apimodels.GrafanaBackend || body.GrafanaManagedCondition == nil { if body.Type() != apimodels.GrafanaBackend || body.GrafanaManagedCondition == nil {
return response.Error(http.StatusBadRequest, "unexpected payload", nil) return ErrResp(http.StatusBadRequest, errors.New("unexpected payload"), "")
} }
return conditionEval(c, *body.GrafanaManagedCondition, srv.DatasourceCache, srv.DataService, srv.Cfg) return conditionEval(c, *body.GrafanaManagedCondition, srv.DatasourceCache, srv.DataService, srv.Cfg)
} }
if body.Type() != apimodels.LoTexRulerBackend { if body.Type() != apimodels.LoTexRulerBackend {
return response.Error(http.StatusBadRequest, "unexpected payload", nil) return ErrResp(http.StatusBadRequest, errors.New("unexpected payload"), "")
} }
var path string var path string
if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil { if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil {
ds, err := srv.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) ds, err := srv.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to get datasource", err) return ErrResp(http.StatusInternalServerError, err, "failed to get datasource")
} }
switch ds.Type { switch ds.Type {
@@ -56,14 +57,14 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodel
case "prometheus": case "prometheus":
path = "api/v1/query" path = "api/v1/query"
default: default:
return response.Error(http.StatusBadRequest, fmt.Sprintf("unexpected recipient type %s", ds.Type), nil) return ErrResp(http.StatusBadRequest, fmt.Errorf("unexpected recipient type %s", ds.Type), "")
} }
} }
t := timeNow() t := timeNow()
queryURL, err := url.Parse(path) queryURL, err := url.Parse(path)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "failed to parse url", err) return ErrResp(http.StatusInternalServerError, err, "failed to parse url")
} }
params := queryURL.Query() params := queryURL.Query()
params.Set("query", body.Expr) params.Set("query", body.Expr)
@@ -86,13 +87,13 @@ func (srv TestingApiSrv) RouteEvalQueries(c *models.ReqContext, cmd apimodels.Ev
} }
if _, err := validateQueriesAndExpressions(cmd.Data, c.SignedInUser, c.SkipCache, srv.DatasourceCache); err != nil { if _, err := validateQueriesAndExpressions(cmd.Data, c.SignedInUser, c.SkipCache, srv.DatasourceCache); err != nil {
return response.Error(http.StatusBadRequest, "invalid queries or expressions", err) return ErrResp(http.StatusBadRequest, err, "invalid queries or expressions")
} }
evaluator := eval.Evaluator{Cfg: srv.Cfg} evaluator := eval.Evaluator{Cfg: srv.Cfg}
evalResults, err := evaluator.QueriesAndExpressionsEval(c.SignedInUser.OrgId, cmd.Data, now, srv.DataService) evalResults, err := evaluator.QueriesAndExpressionsEval(c.SignedInUser.OrgId, cmd.Data, now, srv.DataService)
if err != nil { if err != nil {
return response.Error(http.StatusBadRequest, "Failed to evaluate queries and expressions", err) return ErrResp(http.StatusBadRequest, err, "Failed to evaluate queries and expressions")
} }
return response.JSONStreaming(http.StatusOK, evalResults) return response.JSONStreaming(http.StatusOK, evalResults)

View File

@@ -27,7 +27,7 @@ func NewForkedRuler(datasourceCache datasources.CacheService, lotex, grafana Rul
func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response { func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
t, err := backendType(ctx, r.DatasourceCache) t, err := backendType(ctx, r.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
switch t { switch t {
case apimodels.GrafanaBackend: case apimodels.GrafanaBackend:
@@ -35,14 +35,14 @@ func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) re
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return r.LotexRuler.RouteDeleteNamespaceRulesConfig(ctx) return r.LotexRuler.RouteDeleteNamespaceRulesConfig(ctx)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
} }
} }
func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response { func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
t, err := backendType(ctx, r.DatasourceCache) t, err := backendType(ctx, r.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
switch t { switch t {
case apimodels.GrafanaBackend: case apimodels.GrafanaBackend:
@@ -50,14 +50,14 @@ func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) respons
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return r.LotexRuler.RouteDeleteRuleGroupConfig(ctx) return r.LotexRuler.RouteDeleteRuleGroupConfig(ctx)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
} }
} }
func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response { func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
t, err := backendType(ctx, r.DatasourceCache) t, err := backendType(ctx, r.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
switch t { switch t {
case apimodels.GrafanaBackend: case apimodels.GrafanaBackend:
@@ -65,14 +65,14 @@ func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) respo
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return r.LotexRuler.RouteGetNamespaceRulesConfig(ctx) return r.LotexRuler.RouteGetNamespaceRulesConfig(ctx)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
} }
} }
func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response { func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
t, err := backendType(ctx, r.DatasourceCache) t, err := backendType(ctx, r.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
switch t { switch t {
case apimodels.GrafanaBackend: case apimodels.GrafanaBackend:
@@ -80,14 +80,14 @@ func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return r.LotexRuler.RouteGetRulegGroupConfig(ctx) return r.LotexRuler.RouteGetRulegGroupConfig(ctx)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
} }
} }
func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response { func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
t, err := backendType(ctx, r.DatasourceCache) t, err := backendType(ctx, r.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
switch t { switch t {
case apimodels.GrafanaBackend: case apimodels.GrafanaBackend:
@@ -95,27 +95,19 @@ func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Respo
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return r.LotexRuler.RouteGetRulesConfig(ctx) return r.LotexRuler.RouteGetRulesConfig(ctx)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
} }
} }
func (r *ForkedRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.PostableRuleGroupConfig) response.Response { func (r *ForkedRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.PostableRuleGroupConfig) response.Response {
backendType, err := backendType(ctx, r.DatasourceCache) backendType, err := backendType(ctx, r.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
payloadType := conf.Type() payloadType := conf.Type()
if backendType != payloadType { if backendType != payloadType {
return response.Error( return ErrResp(400, fmt.Errorf("unexpected backend type (%v) vs payload type (%v)", backendType, payloadType), "")
400,
fmt.Sprintf(
"unexpected backend type (%v) vs payload type (%v)",
backendType,
payloadType,
),
nil,
)
} }
switch backendType { switch backendType {
@@ -124,6 +116,6 @@ func (r *ForkedRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apim
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return r.LotexRuler.RoutePostNameRulesConfig(ctx, conf) return r.LotexRuler.RoutePostNameRulesConfig(ctx, conf)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", backendType), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", backendType), "")
} }
} }

View File

@@ -42,7 +42,7 @@ func (am *ForkedAMSvc) getService(ctx *models.ReqContext) (AlertmanagerApiServic
func (am *ForkedAMSvc) RouteCreateSilence(ctx *models.ReqContext, body apimodels.PostableSilence) response.Response { func (am *ForkedAMSvc) RouteCreateSilence(ctx *models.ReqContext, body apimodels.PostableSilence) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteCreateSilence(ctx, body) return s.RouteCreateSilence(ctx, body)
@@ -51,7 +51,7 @@ func (am *ForkedAMSvc) RouteCreateSilence(ctx *models.ReqContext, body apimodels
func (am *ForkedAMSvc) RouteDeleteAlertingConfig(ctx *models.ReqContext) response.Response { func (am *ForkedAMSvc) RouteDeleteAlertingConfig(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteDeleteAlertingConfig(ctx) return s.RouteDeleteAlertingConfig(ctx)
@@ -60,7 +60,7 @@ func (am *ForkedAMSvc) RouteDeleteAlertingConfig(ctx *models.ReqContext) respons
func (am *ForkedAMSvc) RouteDeleteSilence(ctx *models.ReqContext) response.Response { func (am *ForkedAMSvc) RouteDeleteSilence(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteDeleteSilence(ctx) return s.RouteDeleteSilence(ctx)
@@ -69,7 +69,7 @@ func (am *ForkedAMSvc) RouteDeleteSilence(ctx *models.ReqContext) response.Respo
func (am *ForkedAMSvc) RouteGetAlertingConfig(ctx *models.ReqContext) response.Response { func (am *ForkedAMSvc) RouteGetAlertingConfig(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteGetAlertingConfig(ctx) return s.RouteGetAlertingConfig(ctx)
@@ -78,7 +78,7 @@ func (am *ForkedAMSvc) RouteGetAlertingConfig(ctx *models.ReqContext) response.R
func (am *ForkedAMSvc) RouteGetAMAlertGroups(ctx *models.ReqContext) response.Response { func (am *ForkedAMSvc) RouteGetAMAlertGroups(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteGetAMAlertGroups(ctx) return s.RouteGetAMAlertGroups(ctx)
@@ -87,7 +87,7 @@ func (am *ForkedAMSvc) RouteGetAMAlertGroups(ctx *models.ReqContext) response.Re
func (am *ForkedAMSvc) RouteGetAMAlerts(ctx *models.ReqContext) response.Response { func (am *ForkedAMSvc) RouteGetAMAlerts(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteGetAMAlerts(ctx) return s.RouteGetAMAlerts(ctx)
@@ -96,7 +96,7 @@ func (am *ForkedAMSvc) RouteGetAMAlerts(ctx *models.ReqContext) response.Respons
func (am *ForkedAMSvc) RouteGetSilence(ctx *models.ReqContext) response.Response { func (am *ForkedAMSvc) RouteGetSilence(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteGetSilence(ctx) return s.RouteGetSilence(ctx)
@@ -105,7 +105,7 @@ func (am *ForkedAMSvc) RouteGetSilence(ctx *models.ReqContext) response.Response
func (am *ForkedAMSvc) RouteGetSilences(ctx *models.ReqContext) response.Response { func (am *ForkedAMSvc) RouteGetSilences(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RouteGetSilences(ctx) return s.RouteGetSilences(ctx)
@@ -114,20 +114,16 @@ func (am *ForkedAMSvc) RouteGetSilences(ctx *models.ReqContext) response.Respons
func (am *ForkedAMSvc) RoutePostAlertingConfig(ctx *models.ReqContext, body apimodels.PostableUserConfig) response.Response { func (am *ForkedAMSvc) RoutePostAlertingConfig(ctx *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
b, err := backendType(ctx, am.DatasourceCache) b, err := backendType(ctx, am.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
if err := body.AlertmanagerConfig.ReceiverType().MatchesBackend(b); err != nil { if err := body.AlertmanagerConfig.ReceiverType().MatchesBackend(b); err != nil {
return response.Error( return ErrResp(400, err, "bad match")
400,
"bad match",
err,
)
} }
return s.RoutePostAlertingConfig(ctx, body) return s.RoutePostAlertingConfig(ctx, body)
@@ -136,7 +132,7 @@ func (am *ForkedAMSvc) RoutePostAlertingConfig(ctx *models.ReqContext, body apim
func (am *ForkedAMSvc) RoutePostAMAlerts(ctx *models.ReqContext, body apimodels.PostableAlerts) response.Response { func (am *ForkedAMSvc) RoutePostAMAlerts(ctx *models.ReqContext, body apimodels.PostableAlerts) response.Response {
s, err := am.getService(ctx) s, err := am.getService(ctx)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
return s.RoutePostAMAlerts(ctx, body) return s.RoutePostAMAlerts(ctx, body)

View File

@@ -26,7 +26,7 @@ func NewForkedProm(datasourceCache datasources.CacheService, proxy, grafana Prom
func (p *ForkedPromSvc) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response { func (p *ForkedPromSvc) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response {
t, err := backendType(ctx, p.DatasourceCache) t, err := backendType(ctx, p.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
switch t { switch t {
@@ -35,14 +35,14 @@ func (p *ForkedPromSvc) RouteGetAlertStatuses(ctx *models.ReqContext) response.R
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return p.ProxySvc.RouteGetAlertStatuses(ctx) return p.ProxySvc.RouteGetAlertStatuses(ctx)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
} }
} }
func (p *ForkedPromSvc) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response { func (p *ForkedPromSvc) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response {
t, err := backendType(ctx, p.DatasourceCache) t, err := backendType(ctx, p.DatasourceCache)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(400, err, "")
} }
switch t { switch t {
@@ -51,6 +51,6 @@ func (p *ForkedPromSvc) RouteGetRuleStatuses(ctx *models.ReqContext) response.Re
case apimodels.LoTexRulerBackend: case apimodels.LoTexRulerBackend:
return p.ProxySvc.RouteGetRuleStatuses(ctx) return p.ProxySvc.RouteGetRuleStatuses(ctx)
default: default:
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil) return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
} }
} }

View File

@@ -36,7 +36,7 @@ func NewLotexAM(proxy *AlertingProxy, log log.Logger) *LotexAM {
func (am *LotexAM) RouteCreateSilence(ctx *models.ReqContext, silenceBody apimodels.PostableSilence) response.Response { func (am *LotexAM) RouteCreateSilence(ctx *models.ReqContext, silenceBody apimodels.PostableSilence) response.Response {
blob, err := json.Marshal(silenceBody) blob, err := json.Marshal(silenceBody)
if err != nil { if err != nil {
return response.Error(500, "Failed marshal silence", err) return ErrResp(500, err, "Failed marshal silence")
} }
return am.withReq( return am.withReq(
ctx, ctx,
@@ -149,7 +149,7 @@ func (am *LotexAM) RouteGetSilences(ctx *models.ReqContext) response.Response {
func (am *LotexAM) RoutePostAlertingConfig(ctx *models.ReqContext, config apimodels.PostableUserConfig) response.Response { func (am *LotexAM) RoutePostAlertingConfig(ctx *models.ReqContext, config apimodels.PostableUserConfig) response.Response {
yml, err := yaml.Marshal(&config) yml, err := yaml.Marshal(&config)
if err != nil { if err != nil {
return response.Error(500, "Failed marshal alert manager configuration ", err) return ErrResp(500, err, "Failed marshal alert manager configuration ")
} }
return am.withReq( return am.withReq(
@@ -165,7 +165,7 @@ func (am *LotexAM) RoutePostAlertingConfig(ctx *models.ReqContext, config apimod
func (am *LotexAM) RoutePostAMAlerts(ctx *models.ReqContext, alerts apimodels.PostableAlerts) response.Response { func (am *LotexAM) RoutePostAMAlerts(ctx *models.ReqContext, alerts apimodels.PostableAlerts) response.Response {
yml, err := yaml.Marshal(alerts) yml, err := yaml.Marshal(alerts)
if err != nil { if err != nil {
return response.Error(500, "Failed marshal postable alerts", err) return ErrResp(500, err, "Failed marshal postable alerts")
} }
return am.withReq( return am.withReq(

View File

@@ -40,7 +40,7 @@ func NewLotexProm(proxy *AlertingProxy, log log.Logger) *LotexProm {
func (p *LotexProm) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response { func (p *LotexProm) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response {
endpoints, err := p.getEndpoints(ctx) endpoints, err := p.getEndpoints(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return p.withReq( return p.withReq(
@@ -59,7 +59,7 @@ func (p *LotexProm) RouteGetAlertStatuses(ctx *models.ReqContext) response.Respo
func (p *LotexProm) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response { func (p *LotexProm) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response {
endpoints, err := p.getEndpoints(ctx) endpoints, err := p.getEndpoints(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return p.withReq( return p.withReq(

View File

@@ -34,7 +34,7 @@ func NewLotexRuler(proxy *AlertingProxy, log log.Logger) *LotexRuler {
func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response { func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
legacyRulerPrefix, err := r.getPrefix(ctx) legacyRulerPrefix, err := r.getPrefix(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(500, err, "")
} }
return r.withReq( return r.withReq(
ctx, ctx,
@@ -52,7 +52,7 @@ func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) res
func (r *LotexRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response { func (r *LotexRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
legacyRulerPrefix, err := r.getPrefix(ctx) legacyRulerPrefix, err := r.getPrefix(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(500, err, "")
} }
return r.withReq( return r.withReq(
ctx, ctx,
@@ -75,7 +75,7 @@ func (r *LotexRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response
func (r *LotexRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response { func (r *LotexRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
legacyRulerPrefix, err := r.getPrefix(ctx) legacyRulerPrefix, err := r.getPrefix(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(500, err, "")
} }
return r.withReq( return r.withReq(
ctx, ctx,
@@ -97,7 +97,7 @@ func (r *LotexRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) respon
func (r *LotexRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response { func (r *LotexRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
legacyRulerPrefix, err := r.getPrefix(ctx) legacyRulerPrefix, err := r.getPrefix(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(500, err, "")
} }
return r.withReq( return r.withReq(
ctx, ctx,
@@ -120,7 +120,7 @@ func (r *LotexRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.R
func (r *LotexRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response { func (r *LotexRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
legacyRulerPrefix, err := r.getPrefix(ctx) legacyRulerPrefix, err := r.getPrefix(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(500, err, "")
} }
return r.withReq( return r.withReq(
ctx, ctx,
@@ -138,11 +138,11 @@ func (r *LotexRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Respon
func (r *LotexRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.PostableRuleGroupConfig) response.Response { func (r *LotexRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.PostableRuleGroupConfig) response.Response {
legacyRulerPrefix, err := r.getPrefix(ctx) legacyRulerPrefix, err := r.getPrefix(ctx)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(500, err, "")
} }
yml, err := yaml.Marshal(conf) yml, err := yaml.Marshal(conf)
if err != nil { if err != nil {
return response.Error(500, "Failed marshal rule group", err) return ErrResp(500, err, "Failed marshal rule group")
} }
ns := ctx.Params("Namespace") ns := ctx.Params("Namespace")
u := withPath(*ctx.Req.URL, fmt.Sprintf("%s/%s", legacyRulerPrefix, ns)) u := withPath(*ctx.Req.URL, fmt.Sprintf("%s/%s", legacyRulerPrefix, ns))

View File

@@ -22,12 +22,15 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/pkg/errors"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var searchRegex = regexp.MustCompile(`\{(\w+)\}`) var searchRegex = regexp.MustCompile(`\{(\w+)\}`)
var NotImplementedResp = ErrResp(http.StatusNotImplemented, errors.New("endpoint not implemented"), "")
func toMacaronPath(path string) string { func toMacaronPath(path string) string {
return string(searchRegex.ReplaceAllFunc([]byte(path), func(s []byte) []byte { return string(searchRegex.ReplaceAllFunc([]byte(path), func(s []byte) []byte {
m := string(s[1 : len(s)-1]) m := string(s[1 : len(s)-1])
@@ -93,7 +96,7 @@ func (p *AlertingProxy) withReq(
) response.Response { ) response.Response {
req, err := http.NewRequest(method, u.String(), body) req, err := http.NewRequest(method, u.String(), body)
if err != nil { if err != nil {
return response.Error(400, err.Error(), nil) return ErrResp(http.StatusBadRequest, err, "")
} }
for h, v := range headers { for h, v := range headers {
req.Header.Add(h, v) req.Header.Add(h, v)
@@ -116,17 +119,17 @@ func (p *AlertingProxy) withReq(
} }
} }
} }
return response.Error(status, errMessage, nil) return ErrResp(status, errors.New(errMessage), "")
} }
t, err := extractor(resp) t, err := extractor(resp)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
b, err := json.Marshal(t) b, err := json.Marshal(t)
if err != nil { if err != nil {
return response.Error(500, err.Error(), nil) return ErrResp(http.StatusInternalServerError, err, "")
} }
return response.JSON(status, b) return response.JSON(status, b)
@@ -222,7 +225,7 @@ func conditionEval(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand,
Data: cmd.Data, Data: cmd.Data,
} }
if err := validateCondition(evalCond, c.SignedInUser, c.SkipCache, datasourceCache); err != nil { if err := validateCondition(evalCond, c.SignedInUser, c.SkipCache, datasourceCache); err != nil {
return response.Error(400, "invalid condition", err) return ErrResp(http.StatusBadRequest, err, "invalid condition")
} }
now := cmd.Now now := cmd.Now
@@ -233,7 +236,7 @@ func conditionEval(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand,
evaluator := eval.Evaluator{Cfg: cfg} evaluator := eval.Evaluator{Cfg: cfg}
evalResults, err := evaluator.ConditionEval(&evalCond, now, dataService) evalResults, err := evaluator.ConditionEval(&evalCond, now, dataService)
if err != nil { if err != nil {
return response.Error(http.StatusBadRequest, "Failed to evaluate conditions", err) return ErrResp(http.StatusBadRequest, err, "Failed to evaluate conditions")
} }
frame := evalResults.AsDataFrame() frame := evalResults.AsDataFrame()
@@ -241,3 +244,11 @@ func conditionEval(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand,
"instances": []*data.Frame{&frame}, "instances": []*data.Frame{&frame},
}) })
} }
// ErrorResp creates a response with a visible error
func ErrResp(status int, err error, msg string, args ...interface{}) *response.NormalResponse {
if msg != "" {
err = errors.WithMessagef(err, msg, args...)
}
return response.Error(status, err.Error(), nil)
}

View File

@@ -61,7 +61,7 @@ func TestAlertmanagerConfigurationIsTransactional(t *testing.T) {
} }
` `
resp := postRequest(t, alertConfigURL, payload, http.StatusBadRequest) // nolint resp := postRequest(t, alertConfigURL, payload, http.StatusBadRequest) // nolint
require.JSONEq(t, "{\"error\":\"alert validation error: token must be specified when using the Slack chat API\", \"message\":\"failed to save and apply Alertmanager configuration\"}", getBody(t, resp.Body)) require.JSONEq(t, "{\"message\":\"failed to save and apply Alertmanager configuration: alert validation error: token must be specified when using the Slack chat API\"}", getBody(t, resp.Body))
resp = getRequest(t, alertConfigURL, http.StatusOK) // nolint resp = getRequest(t, alertConfigURL, http.StatusOK) // nolint
require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body)) require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body))

View File

@@ -84,7 +84,7 @@ func TestAMConfigAccess(t *testing.T) {
desc: "viewer request should fail", desc: "viewer request should fail",
url: "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts", url: "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts",
expStatus: http.StatusForbidden, expStatus: http.StatusForbidden,
expBody: `{"message": "Permission denied"}`, expBody: `{"message": "permission denied"}`,
}, },
{ {
desc: "editor request should succeed", desc: "editor request should succeed",
@@ -146,7 +146,7 @@ func TestAMConfigAccess(t *testing.T) {
desc: "viewer request should fail", desc: "viewer request should fail",
url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silences", url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silences",
expStatus: http.StatusForbidden, expStatus: http.StatusForbidden,
expBody: `{"message": "Permission denied"}`, expBody: `{"message": "permission denied"}`,
}, },
{ {
desc: "editor request should succeed", desc: "editor request should succeed",
@@ -252,7 +252,7 @@ func TestAMConfigAccess(t *testing.T) {
desc: "viewer request should fail", desc: "viewer request should fail",
url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silence/%s", url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silence/%s",
expStatus: http.StatusForbidden, expStatus: http.StatusForbidden,
expBody: `{"message": "Permission denied"}`, expBody: `{"message": "permission denied"}`,
}, },
{ {
desc: "editor request should succeed", desc: "editor request should succeed",
@@ -504,7 +504,7 @@ func TestRulerAccess(t *testing.T) {
desc: "viewer request should fail", desc: "viewer request should fail",
url: "http://viewer:viewer@%s/api/ruler/grafana/api/v1/rules/default", url: "http://viewer:viewer@%s/api/ruler/grafana/api/v1/rules/default",
expStatus: http.StatusForbidden, expStatus: http.StatusForbidden,
expectedResponse: `{"error":"user does not have permissions to edit the namespace", "message":"user does not have permissions to edit the namespace"}`, expectedResponse: `{"message":"user does not have permissions to edit the namespace: user does not have permissions to edit the namespace"}`,
}, },
{ {
desc: "editor request should succeed", desc: "editor request should succeed",
@@ -763,7 +763,7 @@ func TestAlertRuleCRUD(t *testing.T) {
Data: []ngmodels.AlertQuery{}, Data: []ngmodels.AlertQuery{},
}, },
}, },
expectedResponse: `{"error":"invalid alert rule: no queries or expressions are found", "message":"failed to update rule group"}`, expectedResponse: `{"message":"failed to update rule group: invalid alert rule: no queries or expressions are found"}`,
}, },
{ {
desc: "alert rule with empty title", desc: "alert rule with empty title",
@@ -793,7 +793,7 @@ func TestAlertRuleCRUD(t *testing.T) {
}, },
}, },
}, },
expectedResponse: `{"error":"invalid alert rule: title is empty", "message":"failed to update rule group"}`, expectedResponse: `{"message":"failed to update rule group: invalid alert rule: title is empty"}`,
}, },
{ {
desc: "alert rule with too long name", desc: "alert rule with too long name",
@@ -823,7 +823,7 @@ func TestAlertRuleCRUD(t *testing.T) {
}, },
}, },
}, },
expectedResponse: `{"error":"invalid alert rule: name length should not be greater than 190", "message":"failed to update rule group"}`, expectedResponse: `{"message":"failed to update rule group: invalid alert rule: name length should not be greater than 190"}`,
}, },
{ {
desc: "alert rule with too long rulegroup", desc: "alert rule with too long rulegroup",
@@ -853,7 +853,7 @@ func TestAlertRuleCRUD(t *testing.T) {
}, },
}, },
}, },
expectedResponse: `{"error":"invalid alert rule: rule group name length should not be greater than 190", "message":"failed to update rule group"}`, expectedResponse: `{"message":"failed to update rule group: invalid alert rule: rule group name length should not be greater than 190"}`,
}, },
{ {
desc: "alert rule with invalid interval", desc: "alert rule with invalid interval",
@@ -884,7 +884,7 @@ func TestAlertRuleCRUD(t *testing.T) {
}, },
}, },
}, },
expectedResponse: `{"error":"invalid alert rule: interval (1s) should be non-zero and divided exactly by scheduler interval: 10s", "message":"failed to update rule group"}`, expectedResponse: `{"message":"failed to update rule group: invalid alert rule: interval (1s) should be non-zero and divided exactly by scheduler interval: 10s"}`,
}, },
{ {
desc: "alert rule with unknown datasource", desc: "alert rule with unknown datasource",
@@ -914,7 +914,7 @@ func TestAlertRuleCRUD(t *testing.T) {
}, },
}, },
}, },
expectedResponse: `{"error":"invalid query A: data source not found: unknown", "message":"failed to validate alert rule AlwaysFiring"}`, expectedResponse: `{"message":"failed to validate alert rule AlwaysFiring: invalid query A: data source not found: unknown"}`,
}, },
{ {
desc: "alert rule with invalid condition", desc: "alert rule with invalid condition",
@@ -944,7 +944,7 @@ func TestAlertRuleCRUD(t *testing.T) {
}, },
}, },
}, },
expectedResponse: `{"error":"condition B not found in any query or expression: it should be one of: [A]", "message":"failed to validate alert rule AlwaysFiring"}`, expectedResponse: `{"message":"failed to validate alert rule AlwaysFiring: condition B not found in any query or expression: it should be one of: [A]"}`,
}, },
} }
@@ -1233,7 +1233,7 @@ func TestAlertRuleCRUD(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, http.StatusNotFound, resp.StatusCode) assert.Equal(t, http.StatusNotFound, resp.StatusCode)
require.JSONEq(t, `{"error":"failed to get alert rule unknown: could not find alert rule", "message": "failed to update rule group"}`, string(b)) require.JSONEq(t, `{"message":"failed to update rule group: failed to get alert rule unknown: could not find alert rule"}`, string(b))
// let's make sure that rule definitions are not affected by the failed POST request. // let's make sure that rule definitions are not affected by the failed POST request.
u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr) u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
@@ -1412,7 +1412,7 @@ func TestAlertRuleCRUD(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode) require.Equal(t, http.StatusNotFound, resp.StatusCode)
require.JSONEq(t, `{"error":"rule group not found under this namespace", "message": "failed to delete rule group"}`, string(b)) require.JSONEq(t, `{"message":"failed to delete rule group: rule group not found under this namespace"}`, string(b))
}) })
t.Run("succeed if the rule group name does exist", func(t *testing.T) { t.Run("succeed if the rule group name does exist", func(t *testing.T) {
@@ -1707,7 +1707,7 @@ func TestEval(t *testing.T) {
} }
`, `,
expectedStatusCode: http.StatusBadRequest, expectedStatusCode: http.StatusBadRequest,
expectedResponse: `{"error":"condition B not found in any query or expression: it should be one of: [A]","message":"invalid condition"}`, expectedResponse: `{"message":"invalid condition: condition B not found in any query or expression: it should be one of: [A]"}`,
}, },
{ {
desc: "unknown query datasource", desc: "unknown query datasource",
@@ -1732,7 +1732,7 @@ func TestEval(t *testing.T) {
} }
`, `,
expectedStatusCode: http.StatusBadRequest, expectedStatusCode: http.StatusBadRequest,
expectedResponse: `{"error":"invalid query A: data source not found: unknown","message":"invalid condition"}`, expectedResponse: `{"message":"invalid condition: invalid query A: data source not found: unknown"}`,
}, },
} }
@@ -1888,7 +1888,7 @@ func TestEval(t *testing.T) {
} }
`, `,
expectedStatusCode: http.StatusBadRequest, expectedStatusCode: http.StatusBadRequest,
expectedResponse: `{"error":"invalid query A: data source not found: unknown","message":"invalid queries or expressions"}`, expectedResponse: `{"message":"invalid queries or expressions: invalid query A: data source not found: unknown"}`,
}, },
} }