From c9e5088e8bca1fe9c3727cb3b52a9fcd488cd12a Mon Sep 17 00:00:00 2001 From: gotjosh Date: Fri, 9 Apr 2021 10:55:41 +0100 Subject: [PATCH] Alerting: Cleanup and move legacy to a legacy file (#32803) * Alerting: Cleanup and move legacy to a legacy file A quick cleanup of the ngalert/api directory, optimising for an easy removal of what is will be considered legacy at some point. A quick summary of what's done is: - Add a prefix `generated` prefix to files that are auto-generated by our swagger definitions. - Create a legacy file to place all the legacy API routes implementation and helpers. Deleting files that where no longer needed after this move. - Rename the `lotex` file to `lotex_ruler` - Adding a couple of comments here and there. With this, I hope to organise our code in this directory a bit better given there's a lot going on. --- go.sum | 1 + pkg/services/ngalert/api/api.go | 245 +--------------- .../ngalert/api/api_alertmanager_base.go | 118 -------- pkg/services/ngalert/api/api_ruler_base.go | 94 ------ pkg/services/ngalert/api/fork_ruler.go | 1 + pkg/services/ngalert/api/forked_am.go | 1 + pkg/services/ngalert/api/forked_prom.go | 1 + .../api/generated_base_api_alertmanager.go | 45 +++ ...se.go => generated_base_api_prometheus.go} | 23 +- .../ngalert/api/generated_base_api_ruler.go | 37 +++ ..._base.go => generated_base_api_testing.go} | 22 +- pkg/services/ngalert/api/instance_api.go | 19 -- pkg/services/ngalert/api/legacy_api_routes.go | 274 ++++++++++++++++++ .../{api_trans_dev.go => legacy_trans_dev.go} | 57 +--- .../ngalert/api/{lotex.go => lotex_ruler.go} | 0 pkg/services/ngalert/api/middleware.go | 23 -- .../ngalert/api/swagger-codegen/Makefile | 3 +- .../api/swagger-codegen/move-and-rename.py | 2 +- .../templates/controller-api.mustache | 17 +- .../templates/partial_header.mustache | 4 +- 20 files changed, 377 insertions(+), 610 deletions(-) delete mode 100644 pkg/services/ngalert/api/api_alertmanager_base.go delete mode 100644 pkg/services/ngalert/api/api_ruler_base.go create mode 100644 pkg/services/ngalert/api/generated_base_api_alertmanager.go rename pkg/services/ngalert/api/{api_prometheus_base.go => generated_base_api_prometheus.go} (52%) create mode 100644 pkg/services/ngalert/api/generated_base_api_ruler.go rename pkg/services/ngalert/api/{api_testing_base.go => generated_base_api_testing.go} (59%) delete mode 100644 pkg/services/ngalert/api/instance_api.go create mode 100644 pkg/services/ngalert/api/legacy_api_routes.go rename pkg/services/ngalert/api/{api_trans_dev.go => legacy_trans_dev.go} (99%) rename pkg/services/ngalert/api/{lotex.go => lotex_ruler.go} (100%) delete mode 100644 pkg/services/ngalert/api/middleware.go diff --git a/go.sum b/go.sum index 3beda12a108..7598296fcfe 100644 --- a/go.sum +++ b/go.sum @@ -1041,6 +1041,7 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc= github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 7805db676d3..81132fc6690 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -1,7 +1,6 @@ package api import ( - "fmt" "time" "github.com/grafana/grafana/pkg/services/ngalert/state" @@ -9,21 +8,16 @@ import ( "github.com/go-macaron/binding" apimodels "github.com/grafana/alerting-api/pkg/api" - "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/middleware" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/ngalert/eval" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/schedule" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb" - "github.com/grafana/grafana/pkg/util" ) // timeNow makes it possible to test usage of time @@ -65,21 +59,25 @@ func (api *API) RegisterAPIEndpoints() { proxy := &AlertingProxy{ DataProxy: api.DataProxy, } + // Register endpoints for proxing to Alertmanager-compatible backends. api.RegisterAlertmanagerApiEndpoints(NewForkedAM( api.DatasourceCache, NewLotexAM(proxy, logger), AlertmanagerSrv{store: api.AlertingStore, am: api.Alertmanager, log: logger}, )) + // Register endpoints for proxing to Prometheus-compatible backends. api.RegisterPrometheusApiEndpoints(NewForkedProm( api.DatasourceCache, NewLotexProm(proxy, logger), PrometheusSrv{log: logger, stateTracker: api.StateTracker}, )) + // Register endpoints for proxing to Cortex Ruler-compatible backends. api.RegisterRulerApiEndpoints(NewForkedRuler( api.DatasourceCache, NewLotexRuler(proxy, logger), RulerSrv{store: api.RuleStore, log: logger}, )) + // Register endpoints for testing evaluation of rules and notification channels. api.RegisterTestingApiEndpoints(TestingApiMock{log: logger}) // Legacy routes; they will be removed in v8 @@ -119,238 +117,3 @@ func (api *API) RegisterAPIEndpoints() { alertInstances.Get("", middleware.ReqSignedIn, routing.Wrap(api.listAlertInstancesEndpoint)) }) } - -// conditionEvalEndpoint handles POST /api/alert-definitions/eval. -func (api *API) conditionEvalEndpoint(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand) response.Response { - evalCond := ngmodels.Condition{ - Condition: cmd.Condition, - OrgID: c.SignedInUser.OrgId, - Data: cmd.Data, - } - if err := api.validateCondition(evalCond, c.SignedInUser, c.SkipCache); err != nil { - return response.Error(400, "invalid condition", err) - } - - now := cmd.Now - if now.IsZero() { - now = timeNow() - } - - evaluator := eval.Evaluator{Cfg: api.Cfg} - evalResults, err := evaluator.ConditionEval(&evalCond, timeNow(), api.DataService) - if err != nil { - return response.Error(400, "Failed to evaluate conditions", err) - } - - frame := evalResults.AsDataFrame() - - return response.JSONStreaming(200, util.DynMap{ - "instances": []*data.Frame{&frame}, - }) -} - -// alertDefinitionEvalEndpoint handles GET /api/alert-definitions/eval/:alertDefinitionUID. -func (api *API) alertDefinitionEvalEndpoint(c *models.ReqContext) response.Response { - alertDefinitionUID := c.Params(":alertDefinitionUID") - - condition, err := api.LoadAlertCondition(alertDefinitionUID, c.SignedInUser.OrgId) - if err != nil { - return response.Error(400, "Failed to load alert definition conditions", err) - } - - if err := api.validateCondition(*condition, c.SignedInUser, c.SkipCache); err != nil { - return response.Error(400, "invalid condition", err) - } - - evaluator := eval.Evaluator{Cfg: api.Cfg} - evalResults, err := evaluator.ConditionEval(condition, timeNow(), api.DataService) - if err != nil { - return response.Error(400, "Failed to evaluate alert", err) - } - frame := evalResults.AsDataFrame() - - return response.JSONStreaming(200, util.DynMap{ - "instances": []*data.Frame{&frame}, - }) -} - -// getAlertDefinitionEndpoint handles GET /api/alert-definitions/:alertDefinitionUID. -func (api *API) getAlertDefinitionEndpoint(c *models.ReqContext) response.Response { - alertDefinitionUID := c.Params(":alertDefinitionUID") - - query := ngmodels.GetAlertDefinitionByUIDQuery{ - UID: alertDefinitionUID, - OrgID: c.SignedInUser.OrgId, - } - - if err := api.Store.GetAlertDefinitionByUID(&query); err != nil { - return response.Error(500, "Failed to get alert definition", err) - } - - return response.JSON(200, &query.Result) -} - -// deleteAlertDefinitionEndpoint handles DELETE /api/alert-definitions/:alertDefinitionUID. -func (api *API) deleteAlertDefinitionEndpoint(c *models.ReqContext) response.Response { - alertDefinitionUID := c.Params(":alertDefinitionUID") - - cmd := ngmodels.DeleteAlertDefinitionByUIDCommand{ - UID: alertDefinitionUID, - OrgID: c.SignedInUser.OrgId, - } - - if err := api.Store.DeleteAlertDefinitionByUID(&cmd); err != nil { - return response.Error(500, "Failed to delete alert definition", err) - } - - return response.Success("Alert definition deleted") -} - -// updateAlertDefinitionEndpoint handles PUT /api/alert-definitions/:alertDefinitionUID. -func (api *API) updateAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionCommand) response.Response { - cmd.UID = c.Params(":alertDefinitionUID") - cmd.OrgID = c.SignedInUser.OrgId - - evalCond := ngmodels.Condition{ - Condition: cmd.Condition, - OrgID: c.SignedInUser.OrgId, - Data: cmd.Data, - } - if err := api.validateCondition(evalCond, c.SignedInUser, c.SkipCache); err != nil { - return response.Error(400, "invalid condition", err) - } - - if err := api.Store.UpdateAlertDefinition(&cmd); err != nil { - return response.Error(500, "Failed to update alert definition", err) - } - - return response.JSON(200, cmd.Result) -} - -// createAlertDefinitionEndpoint handles POST /api/alert-definitions. -func (api *API) createAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.SaveAlertDefinitionCommand) response.Response { - cmd.OrgID = c.SignedInUser.OrgId - - evalCond := ngmodels.Condition{ - Condition: cmd.Condition, - OrgID: c.SignedInUser.OrgId, - Data: cmd.Data, - } - if err := api.validateCondition(evalCond, c.SignedInUser, c.SkipCache); err != nil { - return response.Error(400, "invalid condition", err) - } - - if err := api.Store.SaveAlertDefinition(&cmd); err != nil { - return response.Error(500, "Failed to create alert definition", err) - } - - return response.JSON(200, cmd.Result) -} - -// listAlertDefinitions handles GET /api/alert-definitions. -func (api *API) listAlertDefinitions(c *models.ReqContext) response.Response { - query := ngmodels.ListAlertDefinitionsQuery{OrgID: c.SignedInUser.OrgId} - - if err := api.Store.GetOrgAlertDefinitions(&query); err != nil { - return response.Error(500, "Failed to list alert definitions", err) - } - - return response.JSON(200, util.DynMap{"results": query.Result}) -} - -func (api *API) pauseScheduler() response.Response { - err := api.Schedule.Pause() - if err != nil { - return response.Error(500, "Failed to pause scheduler", err) - } - return response.JSON(200, util.DynMap{"message": "alert definition scheduler paused"}) -} - -func (api *API) unpauseScheduler() response.Response { - err := api.Schedule.Unpause() - if err != nil { - return response.Error(500, "Failed to unpause scheduler", err) - } - return response.JSON(200, util.DynMap{"message": "alert definition scheduler unpaused"}) -} - -// alertDefinitionPauseEndpoint handles POST /api/alert-definitions/pause. -func (api *API) alertDefinitionPauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response { - cmd.OrgID = c.SignedInUser.OrgId - cmd.Paused = true - - err := api.Store.UpdateAlertDefinitionPaused(&cmd) - if err != nil { - return response.Error(500, "Failed to pause alert definition", err) - } - return response.JSON(200, util.DynMap{"message": fmt.Sprintf("%d alert definitions paused", cmd.ResultCount)}) -} - -// alertDefinitionUnpauseEndpoint handles POST /api/alert-definitions/unpause. -func (api *API) alertDefinitionUnpauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response { - cmd.OrgID = c.SignedInUser.OrgId - cmd.Paused = false - - err := api.Store.UpdateAlertDefinitionPaused(&cmd) - if err != nil { - return response.Error(500, "Failed to unpause alert definition", err) - } - return response.JSON(200, util.DynMap{"message": fmt.Sprintf("%d alert definitions unpaused", cmd.ResultCount)}) -} - -// LoadAlertCondition returns a Condition object for the given alertDefinitionID. -func (api *API) LoadAlertCondition(alertDefinitionUID string, orgID int64) (*ngmodels.Condition, error) { - q := ngmodels.GetAlertDefinitionByUIDQuery{UID: alertDefinitionUID, OrgID: orgID} - if err := api.Store.GetAlertDefinitionByUID(&q); err != nil { - return nil, err - } - alertDefinition := q.Result - - err := api.Store.ValidateAlertDefinition(alertDefinition, true) - if err != nil { - return nil, err - } - - return &ngmodels.Condition{ - Condition: alertDefinition.Condition, - OrgID: alertDefinition.OrgID, - Data: alertDefinition.Data, - }, nil -} - -func (api *API) validateCondition(c ngmodels.Condition, user *models.SignedInUser, skipCache bool) error { - var refID string - - if len(c.Data) == 0 { - return nil - } - - for _, query := range c.Data { - if c.Condition == query.RefID { - refID = c.Condition - } - - datasourceUID, err := query.GetDatasource() - if err != nil { - return err - } - - isExpression, err := query.IsExpression() - if err != nil { - return err - } - if isExpression { - continue - } - - _, err = api.DatasourceCache.GetDatasourceByUID(datasourceUID, user, skipCache) - if err != nil { - return fmt.Errorf("failed to get datasource: %s: %w", datasourceUID, err) - } - } - - if refID == "" { - return fmt.Errorf("condition %s not found in any query or expression", c.Condition) - } - return nil -} diff --git a/pkg/services/ngalert/api/api_alertmanager_base.go b/pkg/services/ngalert/api/api_alertmanager_base.go deleted file mode 100644 index 9d70c87c45d..00000000000 --- a/pkg/services/ngalert/api/api_alertmanager_base.go +++ /dev/null @@ -1,118 +0,0 @@ -/*Package api contains base API implementation of unified alerting - * - * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) - * - * Need to remove unused imports. - */ -package api - -import ( - "net/http" - - "github.com/go-macaron/binding" - apimodels "github.com/grafana/alerting-api/pkg/api" - "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/middleware" - "github.com/grafana/grafana/pkg/models" -) - -type AlertmanagerApiService interface { - RouteCreateSilence(*models.ReqContext, apimodels.PostableSilence) response.Response - RouteDeleteAlertingConfig(*models.ReqContext) response.Response - RouteDeleteSilence(*models.ReqContext) response.Response - RouteGetAMAlertGroups(*models.ReqContext) response.Response - RouteGetAMAlerts(*models.ReqContext) response.Response - RouteGetAlertingConfig(*models.ReqContext) response.Response - RouteGetSilence(*models.ReqContext) response.Response - RouteGetSilences(*models.ReqContext) response.Response - RoutePostAMAlerts(*models.ReqContext, apimodels.PostableAlerts) response.Response - RoutePostAlertingConfig(*models.ReqContext, apimodels.PostableUserConfig) response.Response -} - -type AlertmanagerApiBase struct { - log log.Logger -} - -func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiService) { - api.RouteRegister.Group("", func(group routing.RouteRegister) { - group.Post(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silences"), binding.Bind(apimodels.PostableSilence{}), routing.Wrap(srv.RouteCreateSilence)) - group.Delete(toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteDeleteAlertingConfig)) - group.Delete(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteDeleteSilence)) - group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts/groups"), routing.Wrap(srv.RouteGetAMAlertGroups)) - group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts"), routing.Wrap(srv.RouteGetAMAlerts)) - group.Get(toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteGetAlertingConfig)) - group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteGetSilence)) - group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silences"), routing.Wrap(srv.RouteGetSilences)) - group.Post(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts"), binding.Bind(apimodels.PostableAlerts{}), routing.Wrap(srv.RoutePostAMAlerts)) - group.Post(toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), binding.Bind(apimodels.PostableUserConfig{}), routing.Wrap(srv.RoutePostAlertingConfig)) - }, middleware.ReqSignedIn) -} - -func (base AlertmanagerApiBase) RouteCreateSilence(c *models.ReqContext, body apimodels.PostableSilence) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteCreateSilence: ", "Recipient", recipient) - base.log.Info("RouteCreateSilence: ", "body", body) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RouteDeleteAlertingConfig(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteDeleteAlertingConfig: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RouteDeleteSilence(c *models.ReqContext) response.Response { - silenceId := c.Params(":SilenceId") - base.log.Info("RouteDeleteSilence: ", "SilenceId", silenceId) - recipient := c.Params(":Recipient") - base.log.Info("RouteDeleteSilence: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RouteGetAMAlertGroups(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetAMAlertGroups: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RouteGetAMAlerts(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetAMAlerts: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RouteGetAlertingConfig(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetAlertingConfig: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RouteGetSilence(c *models.ReqContext) response.Response { - silenceId := c.Params(":SilenceId") - base.log.Info("RouteGetSilence: ", "SilenceId", silenceId) - recipient := c.Params(":Recipient") - base.log.Info("RouteGetSilence: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RouteGetSilences(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetSilences: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RoutePostAMAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RoutePostAMAlerts: ", "Recipient", recipient) - base.log.Info("RoutePostAMAlerts: ", "body", body) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base AlertmanagerApiBase) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RoutePostAlertingConfig: ", "Recipient", recipient) - base.log.Info("RoutePostAlertingConfig: ", "body", body) - return response.Error(http.StatusNotImplemented, "", nil) -} diff --git a/pkg/services/ngalert/api/api_ruler_base.go b/pkg/services/ngalert/api/api_ruler_base.go deleted file mode 100644 index aed73088e6b..00000000000 --- a/pkg/services/ngalert/api/api_ruler_base.go +++ /dev/null @@ -1,94 +0,0 @@ -/*Package api contains base API implementation of unified alerting - * - * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) - * - * Need to remove unused imports. - */ -package api - -import ( - "net/http" - - "github.com/go-macaron/binding" - apimodels "github.com/grafana/alerting-api/pkg/api" - "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/middleware" - "github.com/grafana/grafana/pkg/models" -) - -type RulerApiService interface { - RouteDeleteNamespaceRulesConfig(*models.ReqContext) response.Response - RouteDeleteRuleGroupConfig(*models.ReqContext) response.Response - RouteGetNamespaceRulesConfig(*models.ReqContext) response.Response - RouteGetRulegGroupConfig(*models.ReqContext) response.Response - RouteGetRulesConfig(*models.ReqContext) response.Response - RoutePostNameRulesConfig(*models.ReqContext, apimodels.PostableRuleGroupConfig) response.Response -} - -type RulerApiBase struct { - log log.Logger -} - -func (api *API) RegisterRulerApiEndpoints(srv RulerApiService) { - api.RouteRegister.Group("", func(group routing.RouteRegister) { - group.Delete(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), routing.Wrap(srv.RouteDeleteNamespaceRulesConfig)) - group.Delete(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), routing.Wrap(srv.RouteDeleteRuleGroupConfig)) - group.Get(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), routing.Wrap(srv.RouteGetNamespaceRulesConfig)) - group.Get(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), routing.Wrap(srv.RouteGetRulegGroupConfig)) - group.Get(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules"), routing.Wrap(srv.RouteGetRulesConfig)) - group.Post(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), binding.Bind(apimodels.PostableRuleGroupConfig{}), routing.Wrap(srv.RoutePostNameRulesConfig)) - }, middleware.ReqSignedIn) -} - -func (base RulerApiBase) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteDeleteNamespaceRulesConfig: ", "Recipient", recipient) - namespace := c.Params(":Namespace") - base.log.Info("RouteDeleteNamespaceRulesConfig: ", "Namespace", namespace) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base RulerApiBase) RouteDeleteRuleGroupConfig(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteDeleteRuleGroupConfig: ", "Recipient", recipient) - namespace := c.Params(":Namespace") - base.log.Info("RouteDeleteRuleGroupConfig: ", "Namespace", namespace) - groupname := c.Params(":Groupname") - base.log.Info("RouteDeleteRuleGroupConfig: ", "Groupname", groupname) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base RulerApiBase) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetNamespaceRulesConfig: ", "Recipient", recipient) - namespace := c.Params(":Namespace") - base.log.Info("RouteGetNamespaceRulesConfig: ", "Namespace", namespace) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base RulerApiBase) RouteGetRulegGroupConfig(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetRulegGroupConfig: ", "Recipient", recipient) - namespace := c.Params(":Namespace") - base.log.Info("RouteGetRulegGroupConfig: ", "Namespace", namespace) - groupname := c.Params(":Groupname") - base.log.Info("RouteGetRulegGroupConfig: ", "Groupname", groupname) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base RulerApiBase) RouteGetRulesConfig(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetRulesConfig: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base RulerApiBase) RoutePostNameRulesConfig(c *models.ReqContext, body apimodels.PostableRuleGroupConfig) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RoutePostNameRulesConfig: ", "Recipient", recipient) - namespace := c.Params(":Namespace") - base.log.Info("RoutePostNameRulesConfig: ", "Namespace", namespace) - base.log.Info("RoutePostNameRulesConfig: ", "body", body) - return response.Error(http.StatusNotImplemented, "", nil) -} diff --git a/pkg/services/ngalert/api/fork_ruler.go b/pkg/services/ngalert/api/fork_ruler.go index 6f8ac48bf68..5cf9859f2f6 100644 --- a/pkg/services/ngalert/api/fork_ruler.go +++ b/pkg/services/ngalert/api/fork_ruler.go @@ -15,6 +15,7 @@ type ForkedRuler struct { DatasourceCache datasources.CacheService } +// NewForkedRuler implements a set of routes that proxy to various Cortex Ruler-compatible backends. func NewForkedRuler(datasourceCache datasources.CacheService, lotex, grafana RulerApiService) *ForkedRuler { return &ForkedRuler{ LotexRuler: lotex, diff --git a/pkg/services/ngalert/api/forked_am.go b/pkg/services/ngalert/api/forked_am.go index dff9e391b11..524c1c0e570 100644 --- a/pkg/services/ngalert/api/forked_am.go +++ b/pkg/services/ngalert/api/forked_am.go @@ -14,6 +14,7 @@ type ForkedAMSvc struct { DatasourceCache datasources.CacheService } +// NewForkedAM implements a set of routes that proxy to various Alertmanager-compatible backends. func NewForkedAM(datasourceCache datasources.CacheService, proxy, grafana AlertmanagerApiService) *ForkedAMSvc { return &ForkedAMSvc{ AMSvc: proxy, diff --git a/pkg/services/ngalert/api/forked_prom.go b/pkg/services/ngalert/api/forked_prom.go index b730f6cf1a8..c7e37668cc0 100644 --- a/pkg/services/ngalert/api/forked_prom.go +++ b/pkg/services/ngalert/api/forked_prom.go @@ -14,6 +14,7 @@ type ForkedPromSvc struct { DatasourceCache datasources.CacheService } +// NewForkedProm implements a set of routes that proxy to various Prometheus-compatible backends. func NewForkedProm(datasourceCache datasources.CacheService, proxy, grafana PrometheusApiService) *ForkedPromSvc { return &ForkedPromSvc{ ProxySvc: proxy, diff --git a/pkg/services/ngalert/api/generated_base_api_alertmanager.go b/pkg/services/ngalert/api/generated_base_api_alertmanager.go new file mode 100644 index 00000000000..50046b71618 --- /dev/null +++ b/pkg/services/ngalert/api/generated_base_api_alertmanager.go @@ -0,0 +1,45 @@ +/*Package api contains base API implementation of unified alerting + * + *Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + * + *Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them. + */ +package api + +import ( + "github.com/go-macaron/binding" + + apimodels "github.com/grafana/alerting-api/pkg/api" + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/models" +) + +type AlertmanagerApiService interface { + RouteCreateSilence(*models.ReqContext, apimodels.PostableSilence) response.Response + RouteDeleteAlertingConfig(*models.ReqContext) response.Response + RouteDeleteSilence(*models.ReqContext) response.Response + RouteGetAMAlertGroups(*models.ReqContext) response.Response + RouteGetAMAlerts(*models.ReqContext) response.Response + RouteGetAlertingConfig(*models.ReqContext) response.Response + RouteGetSilence(*models.ReqContext) response.Response + RouteGetSilences(*models.ReqContext) response.Response + RoutePostAMAlerts(*models.ReqContext, apimodels.PostableAlerts) response.Response + RoutePostAlertingConfig(*models.ReqContext, apimodels.PostableUserConfig) response.Response +} + +func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiService) { + api.RouteRegister.Group("", func(group routing.RouteRegister) { + group.Post(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silences"), binding.Bind(apimodels.PostableSilence{}), routing.Wrap(srv.RouteCreateSilence)) + group.Delete(toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteDeleteAlertingConfig)) + group.Delete(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteDeleteSilence)) + group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts/groups"), routing.Wrap(srv.RouteGetAMAlertGroups)) + group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts"), routing.Wrap(srv.RouteGetAMAlerts)) + group.Get(toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteGetAlertingConfig)) + group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteGetSilence)) + group.Get(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/silences"), routing.Wrap(srv.RouteGetSilences)) + group.Post(toMacaronPath("/api/alertmanager/{Recipient}/api/v2/alerts"), binding.Bind(apimodels.PostableAlerts{}), routing.Wrap(srv.RoutePostAMAlerts)) + group.Post(toMacaronPath("/api/alertmanager/{Recipient}/config/api/v1/alerts"), binding.Bind(apimodels.PostableUserConfig{}), routing.Wrap(srv.RoutePostAlertingConfig)) + }, middleware.ReqSignedIn) +} diff --git a/pkg/services/ngalert/api/api_prometheus_base.go b/pkg/services/ngalert/api/generated_base_api_prometheus.go similarity index 52% rename from pkg/services/ngalert/api/api_prometheus_base.go rename to pkg/services/ngalert/api/generated_base_api_prometheus.go index fd4e123d847..09c196523eb 100644 --- a/pkg/services/ngalert/api/api_prometheus_base.go +++ b/pkg/services/ngalert/api/generated_base_api_prometheus.go @@ -1,17 +1,14 @@ /*Package api contains base API implementation of unified alerting * - * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + *Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) * - * Need to remove unused imports. + *Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them. */ package api import ( - "net/http" - "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" ) @@ -21,25 +18,9 @@ type PrometheusApiService interface { RouteGetRuleStatuses(*models.ReqContext) response.Response } -type PrometheusApiBase struct { - log log.Logger -} - func (api *API) RegisterPrometheusApiEndpoints(srv PrometheusApiService) { api.RouteRegister.Group("", func(group routing.RouteRegister) { group.Get(toMacaronPath("/api/prometheus/{Recipient}/api/v1/alerts"), routing.Wrap(srv.RouteGetAlertStatuses)) group.Get(toMacaronPath("/api/prometheus/{Recipient}/api/v1/rules"), routing.Wrap(srv.RouteGetRuleStatuses)) }, middleware.ReqSignedIn) } - -func (base PrometheusApiBase) RouteGetAlertStatuses(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetAlertStatuses: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base PrometheusApiBase) RouteGetRuleStatuses(c *models.ReqContext) response.Response { - recipient := c.Params(":Recipient") - base.log.Info("RouteGetRuleStatuses: ", "Recipient", recipient) - return response.Error(http.StatusNotImplemented, "", nil) -} diff --git a/pkg/services/ngalert/api/generated_base_api_ruler.go b/pkg/services/ngalert/api/generated_base_api_ruler.go new file mode 100644 index 00000000000..64abea1af13 --- /dev/null +++ b/pkg/services/ngalert/api/generated_base_api_ruler.go @@ -0,0 +1,37 @@ +/*Package api contains base API implementation of unified alerting + * + *Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + * + *Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them. + */ +package api + +import ( + "github.com/go-macaron/binding" + + apimodels "github.com/grafana/alerting-api/pkg/api" + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/models" +) + +type RulerApiService interface { + RouteDeleteNamespaceRulesConfig(*models.ReqContext) response.Response + RouteDeleteRuleGroupConfig(*models.ReqContext) response.Response + RouteGetNamespaceRulesConfig(*models.ReqContext) response.Response + RouteGetRulegGroupConfig(*models.ReqContext) response.Response + RouteGetRulesConfig(*models.ReqContext) response.Response + RoutePostNameRulesConfig(*models.ReqContext, apimodels.PostableRuleGroupConfig) response.Response +} + +func (api *API) RegisterRulerApiEndpoints(srv RulerApiService) { + api.RouteRegister.Group("", func(group routing.RouteRegister) { + group.Delete(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), routing.Wrap(srv.RouteDeleteNamespaceRulesConfig)) + group.Delete(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), routing.Wrap(srv.RouteDeleteRuleGroupConfig)) + group.Get(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), routing.Wrap(srv.RouteGetNamespaceRulesConfig)) + group.Get(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname}"), routing.Wrap(srv.RouteGetRulegGroupConfig)) + group.Get(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules"), routing.Wrap(srv.RouteGetRulesConfig)) + group.Post(toMacaronPath("/api/ruler/{Recipient}/api/v1/rules/{Namespace}"), binding.Bind(apimodels.PostableRuleGroupConfig{}), routing.Wrap(srv.RoutePostNameRulesConfig)) + }, middleware.ReqSignedIn) +} diff --git a/pkg/services/ngalert/api/api_testing_base.go b/pkg/services/ngalert/api/generated_base_api_testing.go similarity index 59% rename from pkg/services/ngalert/api/api_testing_base.go rename to pkg/services/ngalert/api/generated_base_api_testing.go index 1b3357ff582..e90d92de071 100644 --- a/pkg/services/ngalert/api/api_testing_base.go +++ b/pkg/services/ngalert/api/generated_base_api_testing.go @@ -1,19 +1,17 @@ /*Package api contains base API implementation of unified alerting * - * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + *Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) * - * Need to remove unused imports. + *Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them. */ package api import ( - "net/http" - "github.com/go-macaron/binding" + apimodels "github.com/grafana/alerting-api/pkg/api" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" ) @@ -23,23 +21,9 @@ type TestingApiService interface { RouteTestRuleConfig(*models.ReqContext, apimodels.TestRulePayload) response.Response } -type TestingApiBase struct { - log log.Logger -} - func (api *API) RegisterTestingApiEndpoints(srv TestingApiService) { api.RouteRegister.Group("", func(group routing.RouteRegister) { group.Post(toMacaronPath("/api/v1/receiver/test"), binding.Bind(apimodels.ExtendedReceiver{}), routing.Wrap(srv.RouteTestReceiverConfig)) group.Post(toMacaronPath("/api/v1/rule/test"), binding.Bind(apimodels.TestRulePayload{}), routing.Wrap(srv.RouteTestRuleConfig)) }, middleware.ReqSignedIn) } - -func (base TestingApiBase) RouteTestReceiverConfig(c *models.ReqContext, body apimodels.ExtendedReceiver) response.Response { - base.log.Info("RouteTestReceiverConfig: ", "body", body) - return response.Error(http.StatusNotImplemented, "", nil) -} - -func (base TestingApiBase) RouteTestRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response { - base.log.Info("RouteTestRuleConfig: ", "body", body) - return response.Error(http.StatusNotImplemented, "", nil) -} diff --git a/pkg/services/ngalert/api/instance_api.go b/pkg/services/ngalert/api/instance_api.go deleted file mode 100644 index 2a9966ba83b..00000000000 --- a/pkg/services/ngalert/api/instance_api.go +++ /dev/null @@ -1,19 +0,0 @@ -package api - -import ( - ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" - - "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/models" -) - -// listAlertInstancesEndpoint handles GET /api/alert-instances. -func (api *API) listAlertInstancesEndpoint(c *models.ReqContext) response.Response { - cmd := ngmodels.ListAlertInstancesQuery{DefinitionOrgID: c.SignedInUser.OrgId} - - if err := api.Store.ListAlertInstances(&cmd); err != nil { - return response.Error(500, "Failed to list alert instances", err) - } - - return response.JSON(200, cmd.Result) -} diff --git a/pkg/services/ngalert/api/legacy_api_routes.go b/pkg/services/ngalert/api/legacy_api_routes.go new file mode 100644 index 00000000000..f6fab3f3c99 --- /dev/null +++ b/pkg/services/ngalert/api/legacy_api_routes.go @@ -0,0 +1,274 @@ +package api + +import ( + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ngalert/eval" + ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/util" +) + +// listAlertInstancesEndpoint handles GET /api/alert-instances. +func (api *API) listAlertInstancesEndpoint(c *models.ReqContext) response.Response { + cmd := ngmodels.ListAlertInstancesQuery{DefinitionOrgID: c.SignedInUser.OrgId} + + if err := api.Store.ListAlertInstances(&cmd); err != nil { + return response.Error(500, "Failed to list alert instances", err) + } + + return response.JSON(200, cmd.Result) +} + +// conditionEvalEndpoint handles POST /api/alert-definitions/eval. +func (api *API) conditionEvalEndpoint(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand) response.Response { + evalCond := ngmodels.Condition{ + Condition: cmd.Condition, + OrgID: c.SignedInUser.OrgId, + Data: cmd.Data, + } + if err := api.validateCondition(evalCond, c.SignedInUser, c.SkipCache); err != nil { + return response.Error(400, "invalid condition", err) + } + + now := cmd.Now + if now.IsZero() { + now = timeNow() + } + + evaluator := eval.Evaluator{Cfg: api.Cfg} + evalResults, err := evaluator.ConditionEval(&evalCond, timeNow(), api.DataService) + if err != nil { + return response.Error(400, "Failed to evaluate conditions", err) + } + + frame := evalResults.AsDataFrame() + + return response.JSONStreaming(200, util.DynMap{ + "instances": []*data.Frame{&frame}, + }) +} + +// alertDefinitionEvalEndpoint handles GET /api/alert-definitions/eval/:alertDefinitionUID. +func (api *API) alertDefinitionEvalEndpoint(c *models.ReqContext) response.Response { + alertDefinitionUID := c.Params(":alertDefinitionUID") + + condition, err := api.LoadAlertCondition(alertDefinitionUID, c.SignedInUser.OrgId) + if err != nil { + return response.Error(400, "Failed to load alert definition conditions", err) + } + + if err := api.validateCondition(*condition, c.SignedInUser, c.SkipCache); err != nil { + return response.Error(400, "invalid condition", err) + } + + evaluator := eval.Evaluator{Cfg: api.Cfg} + evalResults, err := evaluator.ConditionEval(condition, timeNow(), api.DataService) + if err != nil { + return response.Error(400, "Failed to evaluate alert", err) + } + frame := evalResults.AsDataFrame() + + return response.JSONStreaming(200, util.DynMap{ + "instances": []*data.Frame{&frame}, + }) +} + +// getAlertDefinitionEndpoint handles GET /api/alert-definitions/:alertDefinitionUID. +func (api *API) getAlertDefinitionEndpoint(c *models.ReqContext) response.Response { + alertDefinitionUID := c.Params(":alertDefinitionUID") + + query := ngmodels.GetAlertDefinitionByUIDQuery{ + UID: alertDefinitionUID, + OrgID: c.SignedInUser.OrgId, + } + + if err := api.Store.GetAlertDefinitionByUID(&query); err != nil { + return response.Error(500, "Failed to get alert definition", err) + } + + return response.JSON(200, &query.Result) +} + +// deleteAlertDefinitionEndpoint handles DELETE /api/alert-definitions/:alertDefinitionUID. +func (api *API) deleteAlertDefinitionEndpoint(c *models.ReqContext) response.Response { + alertDefinitionUID := c.Params(":alertDefinitionUID") + + cmd := ngmodels.DeleteAlertDefinitionByUIDCommand{ + UID: alertDefinitionUID, + OrgID: c.SignedInUser.OrgId, + } + + if err := api.Store.DeleteAlertDefinitionByUID(&cmd); err != nil { + return response.Error(500, "Failed to delete alert definition", err) + } + + return response.Success("Alert definition deleted") +} + +// updateAlertDefinitionEndpoint handles PUT /api/alert-definitions/:alertDefinitionUID. +func (api *API) updateAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionCommand) response.Response { + cmd.UID = c.Params(":alertDefinitionUID") + cmd.OrgID = c.SignedInUser.OrgId + + evalCond := ngmodels.Condition{ + Condition: cmd.Condition, + OrgID: c.SignedInUser.OrgId, + Data: cmd.Data, + } + if err := api.validateCondition(evalCond, c.SignedInUser, c.SkipCache); err != nil { + return response.Error(400, "invalid condition", err) + } + + if err := api.Store.UpdateAlertDefinition(&cmd); err != nil { + return response.Error(500, "Failed to update alert definition", err) + } + + return response.JSON(200, cmd.Result) +} + +// createAlertDefinitionEndpoint handles POST /api/alert-definitions. +func (api *API) createAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.SaveAlertDefinitionCommand) response.Response { + cmd.OrgID = c.SignedInUser.OrgId + + evalCond := ngmodels.Condition{ + Condition: cmd.Condition, + OrgID: c.SignedInUser.OrgId, + Data: cmd.Data, + } + if err := api.validateCondition(evalCond, c.SignedInUser, c.SkipCache); err != nil { + return response.Error(400, "invalid condition", err) + } + + if err := api.Store.SaveAlertDefinition(&cmd); err != nil { + return response.Error(500, "Failed to create alert definition", err) + } + + return response.JSON(200, cmd.Result) +} + +// listAlertDefinitions handles GET /api/alert-definitions. +func (api *API) listAlertDefinitions(c *models.ReqContext) response.Response { + query := ngmodels.ListAlertDefinitionsQuery{OrgID: c.SignedInUser.OrgId} + + if err := api.Store.GetOrgAlertDefinitions(&query); err != nil { + return response.Error(500, "Failed to list alert definitions", err) + } + + return response.JSON(200, util.DynMap{"results": query.Result}) +} + +func (api *API) pauseScheduler() response.Response { + err := api.Schedule.Pause() + if err != nil { + return response.Error(500, "Failed to pause scheduler", err) + } + return response.JSON(200, util.DynMap{"message": "alert definition scheduler paused"}) +} + +func (api *API) unpauseScheduler() response.Response { + err := api.Schedule.Unpause() + if err != nil { + return response.Error(500, "Failed to unpause scheduler", err) + } + return response.JSON(200, util.DynMap{"message": "alert definition scheduler unpaused"}) +} + +// alertDefinitionPauseEndpoint handles POST /api/alert-definitions/pause. +func (api *API) alertDefinitionPauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response { + cmd.OrgID = c.SignedInUser.OrgId + cmd.Paused = true + + err := api.Store.UpdateAlertDefinitionPaused(&cmd) + if err != nil { + return response.Error(500, "Failed to pause alert definition", err) + } + return response.JSON(200, util.DynMap{"message": fmt.Sprintf("%d alert definitions paused", cmd.ResultCount)}) +} + +// alertDefinitionUnpauseEndpoint handles POST /api/alert-definitions/unpause. +func (api *API) alertDefinitionUnpauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response { + cmd.OrgID = c.SignedInUser.OrgId + cmd.Paused = false + + err := api.Store.UpdateAlertDefinitionPaused(&cmd) + if err != nil { + return response.Error(500, "Failed to unpause alert definition", err) + } + return response.JSON(200, util.DynMap{"message": fmt.Sprintf("%d alert definitions unpaused", cmd.ResultCount)}) +} + +// LoadAlertCondition returns a Condition object for the given alertDefinitionID. +func (api *API) LoadAlertCondition(alertDefinitionUID string, orgID int64) (*ngmodels.Condition, error) { + q := ngmodels.GetAlertDefinitionByUIDQuery{UID: alertDefinitionUID, OrgID: orgID} + if err := api.Store.GetAlertDefinitionByUID(&q); err != nil { + return nil, err + } + alertDefinition := q.Result + + err := api.Store.ValidateAlertDefinition(alertDefinition, true) + if err != nil { + return nil, err + } + + return &ngmodels.Condition{ + Condition: alertDefinition.Condition, + OrgID: alertDefinition.OrgID, + Data: alertDefinition.Data, + }, nil +} + +func (api *API) validateCondition(c ngmodels.Condition, user *models.SignedInUser, skipCache bool) error { + var refID string + + if len(c.Data) == 0 { + return nil + } + + for _, query := range c.Data { + if c.Condition == query.RefID { + refID = c.Condition + } + + datasourceUID, err := query.GetDatasource() + if err != nil { + return err + } + + isExpression, err := query.IsExpression() + if err != nil { + return err + } + if isExpression { + continue + } + + _, err = api.DatasourceCache.GetDatasourceByUID(datasourceUID, user, skipCache) + if err != nil { + return fmt.Errorf("failed to get datasource: %s: %w", datasourceUID, err) + } + } + + if refID == "" { + return fmt.Errorf("condition %s not found in any query or expression", c.Condition) + } + return nil +} + +func (api *API) validateOrgAlertDefinition(c *models.ReqContext) { + uid := c.ParamsEscape(":alertDefinitionUID") + + if uid == "" { + c.JsonApiErr(403, "Permission denied", nil) + return + } + + query := ngmodels.GetAlertDefinitionByUIDQuery{UID: uid, OrgID: c.SignedInUser.OrgId} + + if err := api.Store.GetAlertDefinitionByUID(&query); err != nil { + c.JsonApiErr(404, "Alert definition not found", nil) + return + } +} diff --git a/pkg/services/ngalert/api/api_trans_dev.go b/pkg/services/ngalert/api/legacy_trans_dev.go similarity index 99% rename from pkg/services/ngalert/api/api_trans_dev.go rename to pkg/services/ngalert/api/legacy_trans_dev.go index ac0cb1fa2cf..080da283ee4 100644 --- a/pkg/services/ngalert/api/api_trans_dev.go +++ b/pkg/services/ngalert/api/legacy_trans_dev.go @@ -4,6 +4,8 @@ import ( "fmt" "time" + "github.com/prometheus/common/model" + apimodels "github.com/grafana/alerting-api/pkg/api" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/api/response" @@ -14,7 +16,6 @@ import ( ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/util" - "github.com/prometheus/common/model" ) // conditionEvalEndpoint handles POST /api/alert-definitions/evalOld. @@ -27,24 +28,19 @@ func (api *API) conditionEvalOldEndpoint(c *models.ReqContext) response.Response if err != nil { return response.Error(400, "Failed to translate alert conditions", err) } - if err := api.validateCondition(*evalCond, c.SignedInUser, c.SkipCache); err != nil { return response.Error(400, "invalid condition", err) } - //now := cmd.Now //if now.IsZero() { //now := timeNow() //} - evaluator := eval.Evaluator{Cfg: api.Cfg} evalResults, err := evaluator.ConditionEval(evalCond, timeNow(), api.DataService) if err != nil { return response.Error(400, "Failed to evaluate conditions", err) } - frame := evalResults.AsDataFrame() - return response.JSONStreaming(200, util.DynMap{ "instances": []*data.Frame{&frame}, }) @@ -56,48 +52,37 @@ func (api *API) conditionEvalOldEndpointByID(c *models.ReqContext) response.Resp if id == 0 { return response.Error(400, "missing id", nil) } - getAlert := &models.GetAlertByIdQuery{ Id: id, } - if err := bus.Dispatch(getAlert); err != nil { return response.Error(400, fmt.Sprintf("could find alert with id %v", id), err) } - if getAlert.Result.OrgId != c.SignedInUser.OrgId { return response.Error(403, "alert does not match organization of user", nil) } - settings := getAlert.Result.Settings - sb, err := settings.ToDB() if err != nil { return response.Error(400, "failed to marshal alert settings", err) } - evalCond, err := translate.DashboardAlertConditions(sb, c.OrgId) if err != nil { return response.Error(400, "Failed to translate alert conditions", err) } - if err := api.validateCondition(*evalCond, c.SignedInUser, c.SkipCache); err != nil { return response.Error(400, "invalid condition", err) } - //now := cmd.Now //if now.IsZero() { //now := timeNow() //} - evaluator := eval.Evaluator{Cfg: api.Cfg} evalResults, err := evaluator.ConditionEval(evalCond, timeNow(), api.DataService) if err != nil { return response.Error(400, "Failed to evaluate conditions", err) } - frame := evalResults.AsDataFrame() - return response.JSONStreaming(200, util.DynMap{ "instances": []*data.Frame{&frame}, }) @@ -109,31 +94,24 @@ func (api *API) conditionOldEndpointByID(c *models.ReqContext) response.Response if id == 0 { return response.Error(400, "missing id", nil) } - getAlert := &models.GetAlertByIdQuery{ Id: id, } - if err := bus.Dispatch(getAlert); err != nil { return response.Error(400, fmt.Sprintf("could find alert with id %v", id), err) } - if getAlert.Result.OrgId != c.SignedInUser.OrgId { return response.Error(403, "alert does not match organization of user", nil) } - settings := getAlert.Result.Settings - sb, err := settings.ToDB() if err != nil { return response.Error(400, "failed to marshal alert settings", err) } - evalCond, err := translate.DashboardAlertConditions(sb, c.OrgId) if err != nil { return response.Error(400, "Failed to translate alert conditions", err) } - return response.JSON(200, evalCond) } @@ -143,32 +121,25 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response { if id == 0 { return response.Error(400, "missing id", nil) } - save := c.Query("save") == "true" - // Get dashboard alert definition from database. oldAlert, status, err := transGetAlertById(id, *c.SignedInUser) if err != nil { return response.Error(status, "failed to get alert", fmt.Errorf("failed to get alert for alert id %v: %w", id, err)) } - // Translate the dashboard's alerts conditions into SSE queries and conditions. sseCond, err := transToSSECondition(oldAlert, *c.SignedInUser) if err != nil { return response.Error(400, "failed to translate alert conditions", fmt.Errorf("failed to translate alert conditions for alert id %v: %w", id, err)) } - // Get the dashboard that contains the dashboard Alert. oldAlertsDash, status, err := transGetAlertsDashById(oldAlert.DashboardId, *c.SignedInUser) if err != nil { return response.Error(status, "failed to get alert's dashboard", fmt.Errorf("failed to get dashboard for alert id %v, %w", id, err)) } - isGeneralFolder := oldAlertsDash.FolderId == 0 && !oldAlertsDash.IsFolder - var namespaceUID string - if isGeneralFolder { namespaceUID = "General" } else { @@ -180,19 +151,15 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response { if err := bus.Dispatch(getFolder); err != nil { return response.Error(400, fmt.Sprintf("could find folder %v for alert with id %v", getFolder.Id, id), err) } - namespaceUID = getFolder.Result.Uid } - noDataSetting, execErrSetting, err := transNoDataExecSettings(oldAlert, *c.SignedInUser) if err != nil { return response.Error(400, "unable to translate nodata/exec error settings", fmt.Errorf("unable to translate nodata/exec error settings for alert id %v: %w", id, err)) } - // TODO: What to do with Rule Tags // ruleTags := map[string]string{} - // for k, v := range oldAlert.Settings.Get("alertRuleTags").MustMap() { // sV, ok := v.(string) // if !ok { @@ -201,9 +168,7 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response { // } // ruleTags[k] = sV // } - // TODO: Need place to put FOR duration - rule := ngmodels.AlertRule{ Title: oldAlert.Name, Data: sseCond.Data, @@ -211,7 +176,6 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response { NoDataState: *noDataSetting, ExecErrState: *execErrSetting, } - rgc := apimodels.PostableRuleGroupConfig{ // TODO? Generate new name on conflict? Name: oldAlert.Name, @@ -220,21 +184,17 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response { toPostableExtendedRuleNode(rule), }, } - cmd := store.UpdateRuleGroupCmd{ OrgID: oldAlert.OrgId, NamespaceUID: namespaceUID, RuleGroupConfig: rgc, } - if !save { return response.JSON(200, cmd) } - // note: Update rule group will set the Interval within the grafana_alert from // the interval of the group. err = api.RuleStore.UpdateRuleGroup(cmd) - if err != nil { return response.JSON(400, util.DynMap{ "message:": "failed to save alert rule", @@ -242,10 +202,8 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response { "cmd": cmd, }) } - return response.JSON(200, cmd) } - func transAdjustInterval(freq int64) model.Duration { // 10 corresponds to the SchedulerCfg, but TODO not worrying about fetching for now. var baseFreq int64 = 10 @@ -254,23 +212,18 @@ func transAdjustInterval(freq int64) model.Duration { } return model.Duration(time.Duration((freq - (freq % baseFreq))) * time.Second) } - func transGetAlertById(id int64, user models.SignedInUser) (*models.Alert, int, error) { getAlert := &models.GetAlertByIdQuery{ Id: id, } - if err := bus.Dispatch(getAlert); err != nil { return nil, 400, fmt.Errorf("could find alert with id %v: %w", id, err) } - if getAlert.Result.OrgId != user.OrgId { return nil, 403, fmt.Errorf("alert does not match organization of user") } - return getAlert.Result, 0, nil } - func transGetAlertsDashById(dashboardId int64, user models.SignedInUser) (*models.Dashboard, int, error) { getDash := &models.GetDashboardQuery{ Id: dashboardId, @@ -281,27 +234,23 @@ func transGetAlertsDashById(dashboardId int64, user models.SignedInUser) (*model } return getDash.Result, 0, nil } - func transToSSECondition(m *models.Alert, user models.SignedInUser) (*ngmodels.Condition, error) { sb, err := m.Settings.ToDB() if err != nil { return nil, fmt.Errorf("failed to marshal alert settings: %w", err) } - evalCond, err := translate.DashboardAlertConditions(sb, user.OrgId) if err != nil { return nil, fmt.Errorf("failed to translate dashboard alert to SSE conditions: %w", err) } return evalCond, nil } - func transNoDataExecSettings(m *models.Alert, user models.SignedInUser) (*ngmodels.NoDataState, *ngmodels.ExecutionErrorState, error) { oldNoData := m.Settings.Get("noDataState").MustString() noDataSetting, err := transNoData(oldNoData) if err != nil { return nil, nil, err } - oldExecErr := m.Settings.Get("executionErrorState").MustString() execErrSetting, err := transExecErr(oldExecErr) if err != nil { @@ -309,7 +258,6 @@ func transNoDataExecSettings(m *models.Alert, user models.SignedInUser) (*ngmode } return &noDataSetting, &execErrSetting, nil } - func transNoData(s string) (ngmodels.NoDataState, error) { switch s { case "ok": @@ -323,7 +271,6 @@ func transNoData(s string) (ngmodels.NoDataState, error) { } return ngmodels.NoData, fmt.Errorf("unrecognized No Data setting %v", s) } - func transExecErr(s string) (ngmodels.ExecutionErrorState, error) { switch s { case "alerting": diff --git a/pkg/services/ngalert/api/lotex.go b/pkg/services/ngalert/api/lotex_ruler.go similarity index 100% rename from pkg/services/ngalert/api/lotex.go rename to pkg/services/ngalert/api/lotex_ruler.go diff --git a/pkg/services/ngalert/api/middleware.go b/pkg/services/ngalert/api/middleware.go deleted file mode 100644 index a31e55bbb17..00000000000 --- a/pkg/services/ngalert/api/middleware.go +++ /dev/null @@ -1,23 +0,0 @@ -package api - -import ( - ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" - - "github.com/grafana/grafana/pkg/models" -) - -func (api *API) validateOrgAlertDefinition(c *models.ReqContext) { - uid := c.ParamsEscape(":alertDefinitionUID") - - if uid == "" { - c.JsonApiErr(403, "Permission denied", nil) - return - } - - query := ngmodels.GetAlertDefinitionByUIDQuery{UID: uid, OrgID: c.SignedInUser.OrgId} - - if err := api.Store.GetAlertDefinitionByUID(&query); err != nil { - c.JsonApiErr(404, "Alert definition not found", nil) - return - } -} diff --git a/pkg/services/ngalert/api/swagger-codegen/Makefile b/pkg/services/ngalert/api/swagger-codegen/Makefile index 887e9799eb1..b10e97f9ac8 100644 --- a/pkg/services/ngalert/api/swagger-codegen/Makefile +++ b/pkg/services/ngalert/api/swagger-codegen/Makefile @@ -18,9 +18,8 @@ fix: sed -i -e 's/apimodels\.\[\]PostableAlert/apimodels.PostableAlerts/' ../go/*.go sed -i -e 's/apimodels\.\[\]UpdateDashboardAclCommand/apimodels.Permissions/' ../go/*.go goimports -w -v ../go/*.go - rm ../go/*.go-e clean: rm -rf ../go -all: swagger-codegen-api fix copy-files clean \ No newline at end of file +all: swagger-codegen-api fix copy-files clean diff --git a/pkg/services/ngalert/api/swagger-codegen/move-and-rename.py b/pkg/services/ngalert/api/swagger-codegen/move-and-rename.py index 02daca12d16..93899b01825 100644 --- a/pkg/services/ngalert/api/swagger-codegen/move-and-rename.py +++ b/pkg/services/ngalert/api/swagger-codegen/move-and-rename.py @@ -4,4 +4,4 @@ files = os.listdir(path) dest_dir = "../" for index, file in enumerate(files): - os.rename(os.path.join(path, file), os.path.join(dest_dir, ''.join([file.split('.')[0], '_base.go']))) \ No newline at end of file + os.rename(os.path.join(path, file), os.path.join(dest_dir, ''.join(['generated_base_',file.split('.')[0], '.go']))) diff --git a/pkg/services/ngalert/api/swagger-codegen/templates/controller-api.mustache b/pkg/services/ngalert/api/swagger-codegen/templates/controller-api.mustache index 3a5a96231f0..16eac0dc90f 100644 --- a/pkg/services/ngalert/api/swagger-codegen/templates/controller-api.mustache +++ b/pkg/services/ngalert/api/swagger-codegen/templates/controller-api.mustache @@ -3,13 +3,11 @@ package {{packageName}} {{#operations}} import ( - "net/http" - "github.com/go-macaron/binding" + "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/infra/log" apimodels "github.com/grafana/alerting-api/pkg/api" "github.com/grafana/grafana/pkg/middleware" ) @@ -18,22 +16,11 @@ type {{classname}}Service interface { {{#operation}} {{nickname}}(*models.ReqContext{{#bodyParams}}, apimodels.{{dataType}}{{/bodyParams}}) response.Response{{/operation}} } -type {{classname}}Base struct { - log log.Logger -} - func (api *API) Register{{classname}}Endpoints(srv {{classname}}Service) { api.RouteRegister.Group("", func(group routing.RouteRegister){ {{#operations}}{{#operation}} group.{{httpMethod}}(toMacaronPath("{{{path}}}"){{#bodyParams}}, binding.Bind(apimodels.{{dataType}}{}){{/bodyParams}}, routing.Wrap(srv.{{nickname}})){{/operation}}{{/operations}} }, middleware.ReqSignedIn) }{{#operation}} - - -func (base {{classname}}Base) {{nickname}}(c *models.ReqContext{{#bodyParams}}, {{paramName}} apimodels.{{dataType}}{{/bodyParams}}) response.Response { {{#pathParams}} - {{paramName}} := c.Params(":{{baseName}}") - base.log.Info("{{nickname}}: ", "{{baseName}}", {{paramName}}){{/pathParams}}{{#bodyParams}} - base.log.Info("{{nickname}}: ", "{{baseName}}", {{paramName}}){{/bodyParams}} - return response.Error(http.StatusNotImplemented, "", nil) -}{{/operation}}{{/operations}} +{{/operation}}{{/operations}} diff --git a/pkg/services/ngalert/api/swagger-codegen/templates/partial_header.mustache b/pkg/services/ngalert/api/swagger-codegen/templates/partial_header.mustache index 5f017f7ccf1..a48cc0a9033 100644 --- a/pkg/services/ngalert/api/swagger-codegen/templates/partial_header.mustache +++ b/pkg/services/ngalert/api/swagger-codegen/templates/partial_header.mustache @@ -1,6 +1,6 @@ /*Package api contains base API implementation of unified alerting * - * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + *Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) * - * Need to remove unused imports. + *Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them. */