[Alerting]: Alertmanager API implementation (#32174)

* Add validation for grafana recipient

* Alertmanager API implementation (WIP)

* Fix encoding/decoding receiver settings from/to YAML

* Save templates together with the configuration

* update POST to apply latest config

* Alertmanager service enabled by the ngalert toggle

* Silence API integration with Alertmanager

* Apply suggestions from code review

Co-authored-by: gotjosh <josue@grafana.com>
Co-authored-by: Ganesh Vernekar <15064823+codesome@users.noreply.github.com>
This commit is contained in:
Sofia Papagiannaki 2021-03-31 23:00:56 +03:00 committed by GitHub
parent b0ffcfd558
commit a5e95823b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 948 additions and 972 deletions

2
go.mod
View File

@ -40,7 +40,7 @@ require (
github.com/google/go-cmp v0.5.5
github.com/google/uuid v1.2.0
github.com/gosimple/slug v1.9.0
github.com/grafana/alerting-api v0.0.0-20210330162237-0b5408c529a8
github.com/grafana/alerting-api v0.0.0-20210331130828-17c19ddf88ee
github.com/grafana/grafana-aws-sdk v0.4.0
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
github.com/grafana/grafana-plugin-sdk-go v0.90.0

4
go.sum
View File

@ -798,6 +798,8 @@ github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
github.com/grafana/alerting-api v0.0.0-20210330162237-0b5408c529a8 h1:okhEX26LU7AGN/3C8NDWfdjBmKclvoFvJz9o/LsNcK8=
github.com/grafana/alerting-api v0.0.0-20210330162237-0b5408c529a8/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
github.com/grafana/alerting-api v0.0.0-20210331130828-17c19ddf88ee h1:jpZdUOta4PK3CH3+2UCuzqn1SGZ+dQj+dWH45B0c1aI=
github.com/grafana/alerting-api v0.0.0-20210331130828-17c19ddf88ee/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA=
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4=
@ -1335,6 +1337,7 @@ github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f/go.mod h1:hoL
github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE=
github.com/prometheus/alertmanager v0.19.0/go.mod h1:Eyp94Yi/T+kdeb2qvq66E3RGuph5T/jm/RBVh4yz1xo=
github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg=
github.com/prometheus/alertmanager v0.21.0 h1:qK51JcUR9l/unhawGA9F9B64OCYfcGewhPNprem/Acc=
github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go=
github.com/prometheus/alertmanager v0.21.1-0.20200911160112-1fdff6b3f939/go.mod h1:imXRHOP6QTsE0fFsIsAV/cXimS32m7gVZOiUj11m6Ig=
github.com/prometheus/alertmanager v0.21.1-0.20210211203738-a7ca7b1d2951/go.mod h1:6Yc2n2ap5/oP99x1yN6Ho+yL0w8a0oClIR5xxW/JLGs=
@ -1421,6 +1424,7 @@ github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17 h1:VN3p3Nb
github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17/go.mod h1:dv3B1syqmkrkmo665MPCU6L8PbTXIiUeg/OEQULLNxA=
github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3 h1:eL7x4/zMnlquMxYe7V078BD7MGskZ0daGln+SJCVzuY=
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3/go.mod h1:P7JlQWFT7jDcFZMtUPQbtGzzzxva3rBn6oIF+LPwFcM=
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=

View File

@ -482,3 +482,18 @@ func (j *Json) MustUint64(args ...uint64) uint64 {
return def
}
// MarshalYAML implements yaml.Marshaller.
func (j *Json) MarshalYAML() (interface{}, error) {
return j.data, nil
}
// UnmarshalYAML implements yaml.Unmarshaller.
func (j *Json) UnmarshalYAML(unmarshal func(interface{}) error) error {
var data interface{}
if err := unmarshal(&data); err != nil {
return err
}
j.data = data
return nil
}

View File

@ -30,6 +30,10 @@ var timeNow = time.Now
type Alertmanager interface {
ApplyConfig(config *apimodels.PostableUserConfig) error
CreateSilence(ps *apimodels.PostableSilence) (string, error)
DeleteSilence(silenceID string) error
GetSilence(silenceID string) (apimodels.GettableSilence, error)
ListSilences(filters []string) (apimodels.GettableSilences, error)
}
// API handlers.
@ -40,6 +44,7 @@ type API struct {
DataService *tsdb.Service
Schedule schedule.ScheduleService
Store store.Store
AlertingStore store.AlertingStore
DataProxy *datasourceproxy.DatasourceProxyService
Alertmanager Alertmanager
}
@ -53,7 +58,7 @@ func (api *API) RegisterAPIEndpoints() {
api.RegisterAlertmanagerApiEndpoints(NewForkedAM(
api.DatasourceCache,
NewLotexAM(proxy, logger),
AlertmanagerApiMock{log: logger},
AlertmanagerSrv{store: api.AlertingStore, am: api.Alertmanager, log: logger},
))
api.RegisterPrometheusApiEndpoints(NewForkedProm(
api.DatasourceCache,

View File

@ -0,0 +1,359 @@
package api
import (
"errors"
"fmt"
"net/http"
"time"
"gopkg.in/yaml.v3"
"github.com/go-openapi/strfmt"
apimodels "github.com/grafana/alerting-api/pkg/api"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/util"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
)
type AlertmanagerSrv struct {
am Alertmanager
store store.AlertingStore
log log.Logger
}
func (srv AlertmanagerSrv) RouteCreateSilence(c *models.ReqContext, postableSilence apimodels.PostableSilence) response.Response {
silenceID, err := srv.am.CreateSilence(&postableSilence)
if err != nil {
if errors.Is(err, notifier.ErrSilenceNotFound) {
return response.Error(http.StatusNotFound, err.Error(), nil)
}
if errors.Is(err, notifier.ErrCreateSilenceBadPayload) {
return response.Error(http.StatusBadRequest, err.Error(), nil)
}
return response.Error(http.StatusInternalServerError, "failed to create silence", err)
}
return response.JSON(http.StatusAccepted, util.DynMap{"message": "silence created", "id": silenceID})
}
func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *models.ReqContext) response.Response {
// not implemented
return response.Error(http.StatusNotImplemented, "", nil)
}
func (srv AlertmanagerSrv) RouteDeleteSilence(c *models.ReqContext) response.Response {
silenceID := c.Params(":SilenceId")
if err := srv.am.DeleteSilence(silenceID); err != nil {
if errors.Is(err, notifier.ErrSilenceNotFound) {
return response.Error(http.StatusNotFound, err.Error(), nil)
}
return response.Error(http.StatusInternalServerError, err.Error(), nil)
}
return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"})
}
func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *models.ReqContext) response.Response {
query := ngmodels.GetLatestAlertmanagerConfigurationQuery{}
err := srv.store.GetLatestAlertmanagerConfiguration(&query)
if err != nil {
return response.Error(http.StatusInternalServerError, "failed to get latest configuration", err)
}
cfg := apimodels.PostableUserConfig{}
err = yaml.Unmarshal([]byte(query.Result.AlertmanagerConfiguration), &cfg)
if err != nil {
return response.Error(http.StatusInternalServerError, "failed to unmarshal alertmanager configuration", err)
}
var apiReceiverName string
var receivers []*apimodels.GettableGrafanaReceiver
alertmanagerCfg := cfg.AlertmanagerConfig
if len(alertmanagerCfg.Receivers) > 0 {
apiReceiverName = alertmanagerCfg.Receivers[0].Name
receivers = make([]*apimodels.GettableGrafanaReceiver, 0, len(alertmanagerCfg.Receivers[0].PostableGrafanaReceivers.GrafanaManagedReceivers))
for _, pr := range alertmanagerCfg.Receivers[0].PostableGrafanaReceivers.GrafanaManagedReceivers {
secureFields := make(map[string]bool, len(pr.SecureSettings))
for k := range pr.SecureSettings {
secureFields[k] = true
}
gr := apimodels.GettableGrafanaReceiver{
Uid: pr.Uid,
Name: pr.Name,
Type: pr.Type,
IsDefault: pr.IsDefault,
SendReminder: pr.SendReminder,
DisableResolveMessage: pr.DisableResolveMessage,
Frequency: pr.Frequency,
Settings: pr.Settings,
SecureFields: secureFields,
}
receivers = append(receivers, &gr)
}
}
gettableApiReceiver := apimodels.GettableApiReceiver{
GettableGrafanaReceivers: apimodels.GettableGrafanaReceivers{
GrafanaManagedReceivers: receivers,
},
}
gettableApiReceiver.Name = apiReceiverName
result := apimodels.GettableUserConfig{
TemplateFiles: cfg.TemplateFiles,
AlertmanagerConfig: apimodels.GettableApiAlertingConfig{
Config: alertmanagerCfg.Config,
Receivers: []*apimodels.GettableApiReceiver{
&gettableApiReceiver,
},
},
}
return response.JSON(http.StatusOK, result)
}
func (srv AlertmanagerSrv) RouteGetAMAlertGroups(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
srv.log.Info("RouteGetAMAlertGroups: ", "Recipient", recipient)
now := time.Now()
result := apimodels.AlertGroups{
&amv2.AlertGroup{
Alerts: []*amv2.GettableAlert{
{
Annotations: amv2.LabelSet{
"annotation1-1": "value1",
"annotation1-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 1"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 1-1"),
},
{
Name: stringPtr("receiver identifier 1-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 1"},
SilencedBy: []string{"silencedBy 1"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label1-1": "value1",
"label1-2": "value2",
},
},
},
{
Annotations: amv2.LabelSet{
"annotation2-1": "value1",
"annotation2-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 2"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 2-1"),
},
{
Name: stringPtr("receiver identifier 2-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 2"},
SilencedBy: []string{"silencedBy 2"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
},
},
},
Labels: amv2.LabelSet{
"label1-1": "value1",
"label1-2": "value2",
},
Receiver: &amv2.Receiver{
Name: stringPtr("receiver identifier 2-1"),
},
},
&amv2.AlertGroup{
Alerts: []*amv2.GettableAlert{
{
Annotations: amv2.LabelSet{
"annotation2-1": "value1",
"annotation2-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 2"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 2-1"),
},
{
Name: stringPtr("receiver identifier 2-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 2"},
SilencedBy: []string{"silencedBy 2"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
},
},
},
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
Receiver: &amv2.Receiver{
Name: stringPtr("receiver identifier 2-1"),
},
},
}
return response.JSON(http.StatusOK, result)
}
func (srv AlertmanagerSrv) RouteGetAMAlerts(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
srv.log.Info("RouteGetAMAlerts: ", "Recipient", recipient)
now := time.Now()
result := apimodels.GettableAlerts{
&amv2.GettableAlert{
Annotations: amv2.LabelSet{
"annotation1-1": "value1",
"annotation1-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 1"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 1-1"),
},
{
Name: stringPtr("receiver identifier 1-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 1"},
SilencedBy: []string{"silencedBy 1"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label1-1": "value1",
"label1-2": "value2",
},
},
},
&amv2.GettableAlert{
Annotations: amv2.LabelSet{
"annotation2-1": "value1",
"annotation2-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 2"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 2-1"),
},
{
Name: stringPtr("receiver identifier 2-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 2"},
SilencedBy: []string{"silencedBy 2"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
},
},
}
return response.JSON(http.StatusOK, result)
}
func (srv AlertmanagerSrv) RouteGetSilence(c *models.ReqContext) response.Response {
silenceID := c.Params(":SilenceId")
gettableSilence, err := srv.am.GetSilence(silenceID)
if err != nil {
if errors.Is(err, notifier.ErrSilenceNotFound) {
return response.Error(http.StatusNotFound, err.Error(), nil)
}
// any other error here should be an unexpected failure and thus an internal error
return response.Error(http.StatusInternalServerError, err.Error(), nil)
}
return response.JSON(http.StatusOK, gettableSilence)
}
func (srv AlertmanagerSrv) RouteGetSilences(c *models.ReqContext) response.Response {
filters := c.QueryStrings("Filter")
gettableSilences, err := srv.am.ListSilences(filters)
if err != nil {
if errors.Is(err, notifier.ErrListSilencesBadPayload) {
return response.Error(http.StatusBadRequest, err.Error(), nil)
}
// any other error here should be an unexpected failure and thus an internal error
return response.Error(http.StatusInternalServerError, err.Error(), nil)
}
return response.JSON(http.StatusOK, gettableSilences)
}
func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
config, err := yaml.Marshal(&body)
if err != nil {
return response.Error(http.StatusInternalServerError, "failed to serialize to the Alertmanager configuration", err)
}
cmd := ngmodels.SaveAlertmanagerConfigurationCmd{
AlertmanagerConfiguration: string(config),
ConfigurationVersion: fmt.Sprintf("v%d", ngmodels.AlertConfigurationVersion),
}
if err := srv.store.SaveAlertmanagerConfiguration(&cmd); err != nil {
return response.Error(http.StatusInternalServerError, "failed to save Alertmanager configuration", err)
}
if err := srv.am.ApplyConfig(&body); err != nil {
return response.Error(http.StatusInternalServerError, "failed to apply Alertmanager configuration", err)
}
return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration created"})
}
func (srv AlertmanagerSrv) RoutePostAMAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response {
// not implemented
return response.Error(http.StatusNotImplemented, "", nil)
}

View File

@ -18,15 +18,14 @@ import (
)
type AlertmanagerApiService interface {
RouteCreateSilence(*models.ReqContext, apimodels.CreateSilenceParams) response.Response
RouteGetSilences(*models.ReqContext) response.Response
RouteGetSilence(*models.ReqContext) response.Response
RouteDeleteSilence(*models.ReqContext) response.Response
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
}
@ -37,44 +36,29 @@ type AlertmanagerApiBase struct {
func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiService) {
api.RouteRegister.Group("", func(group routing.RouteRegister) {
// Silences
group.Post(toMacaronPath("/alertmanager/{Recipient}/api/v2/silences"), binding.Bind(apimodels.CreateSilenceParams{}), routing.Wrap(srv.RouteCreateSilence))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/silences"), routing.Wrap(srv.RouteGetSilences))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteGetSilence))
group.Post(toMacaronPath("/alertmanager/{Recipient}/api/v2/silences"), binding.Bind(apimodels.PostableSilence{}), routing.Wrap(srv.RouteCreateSilence))
group.Delete(toMacaronPath("/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteDeleteAlertingConfig))
group.Delete(toMacaronPath("/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteDeleteSilence))
// Alerts
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts/groups"), routing.Wrap(srv.RouteGetAMAlertGroups))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts"), routing.Wrap(srv.RouteGetAMAlerts))
group.Post(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts"), binding.Bind(apimodels.PostableAlerts{}), routing.Wrap(srv.RoutePostAMAlerts))
// Configuration
group.Delete(toMacaronPath("/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteDeleteAlertingConfig))
group.Get(toMacaronPath("/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteGetAlertingConfig))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteGetSilence))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/silences"), routing.Wrap(srv.RouteGetSilences))
group.Post(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts"), binding.Bind(apimodels.PostableAlerts{}), routing.Wrap(srv.RoutePostAMAlerts))
group.Post(toMacaronPath("/alertmanager/{Recipient}/config/api/v1/alerts"), binding.Bind(apimodels.PostableUserConfig{}), routing.Wrap(srv.RoutePostAlertingConfig))
})
}
func (base AlertmanagerApiBase) RouteCreateSilence(c *models.ReqContext, params apimodels.CreateSilenceParams) response.Response {
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: ", "params", params)
base.log.Info("RouteCreateSilence: ", "body", body)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RouteGetSilences(c *models.ReqContext) response.Response {
func (base AlertmanagerApiBase) RouteDeleteAlertingConfig(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RouteGetSilences: ", "Recipient", recipient)
filter := c.Params(":Filter")
base.log.Info("RouteGetSilences: ", "params", filter)
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)
base.log.Info("RouteDeleteAlertingConfig: ", "Recipient", recipient)
return response.Error(http.StatusNotImplemented, "", nil)
}
@ -86,12 +70,6 @@ func (base AlertmanagerApiBase) RouteDeleteSilence(c *models.ReqContext) respons
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) RouteGetAMAlertGroups(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RouteGetAMAlertGroups: ", "Recipient", recipient)
@ -110,6 +88,20 @@ func (base AlertmanagerApiBase) RouteGetAlertingConfig(c *models.ReqContext) res
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)

View File

@ -1,891 +0,0 @@
/*Package api contains mock 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"
"time"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/go-openapi/strfmt"
apimodels "github.com/grafana/alerting-api/pkg/api"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/config"
)
func toSimpleJSON(blob string) *simplejson.Json {
json, _ := simplejson.NewJson([]byte(blob))
return json
}
var alertmanagerReceiver = models.AlertNotification{
Id: 1,
Uid: "alertmanager UID",
OrgId: 1,
Name: "an alert manager receiver",
Type: "prometheus-alertmanager",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"basicAuthUser": "user",
"httpMethod": "POST",
"severity": "critical",
"uploadImage": false,
"url": "http://localhost:9093"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"basicAuthPassword": "<basicAuthPassword>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var alertmanagerReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&alertmanagerReceiver))
var dingdingReceiver = models.AlertNotification{
Id: 2,
Uid: "dingding UID",
OrgId: 1,
Name: "a dingding receiver",
Type: "dingding",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"httpMethod": "POST",
"msgType": "link",
"severity": "critical",
"uploadImage": false,
"url": "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var dingdingReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&dingdingReceiver))
var discordReceiver = models.AlertNotification{
Id: 3,
Uid: "discord UID",
OrgId: 1,
Name: "a discord receiver",
Type: "discord",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"content": "@user",
"httpMethod": "POST",
"severity": "critical",
"uploadImage": false,
"url": "http://"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var discordReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&discordReceiver))
var emailReceiver = models.AlertNotification{
Id: 4,
Uid: "email UID",
OrgId: 1,
Name: "an email receiver",
Type: "email",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"addresses": "<email>",
"autoResolve": true,
"httpMethod": "POST",
"severity": "critical",
"singleEmail": true,
"uploadImage": false
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var emailReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&emailReceiver))
var googlechatReceiver = models.AlertNotification{
Id: 5,
Uid: "googlechatReceiver UID",
OrgId: 1,
Name: "a googlechat receiver",
Type: "googlechat",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"httpMethod": "POST",
"severity": "critical",
"uploadImage": false,
"url": "http://"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var googlechatReceiverDTOs = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&googlechatReceiver))
var hipchatReceiver = models.AlertNotification{
Id: 6,
Uid: "hipchat UID",
OrgId: 1,
Name: "a hipchat receiver",
Type: "hipchat",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"apiKey": "<apikey>",
"autoResolve": true,
"httpMethod": "POST",
"roomid": "12345",
"severity": "critical",
"uploadImage": false,
"url": "http://"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var hipchatReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&hipchatReceiver))
var kafkaReceiver = models.AlertNotification{
Id: 7,
Uid: "kafka UID",
OrgId: 1,
Name: "a kafka receiver",
Type: "kafka",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"httpMethod": "POST",
"kafkaRestProxy": "http://localhost:8082",
"kafkaTopic": "topic1",
"severity": "critical",
"uploadImage": false
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var kafkaReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&kafkaReceiver))
var lineReceiver = models.AlertNotification{
Id: 8,
Uid: "line UID",
OrgId: 1,
Name: "a line receiver",
Type: "line",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(` "settings": {
"autoResolve": true,
"httpMethod": "POST",
"severity": "critical",
"uploadImage": false
},`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"token": "<token>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var lineReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&lineReceiver))
var opsgenieReceiver = models.AlertNotification{
Id: 9,
Uid: "opsgenie UID",
OrgId: 1,
Name: "a opsgenie receiver",
Type: "opsgenie",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(` "settings": {
"apiUrl": "https://api.opsgenie.com/v2/alerts",
"autoClose": true,
"autoResolve": true,
"httpMethod": "POST",
"overridePriority": true,
"severity": "critical",
"uploadImage": false
},`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"apiKey": "<apiKey>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var opsgenieReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&opsgenieReceiver))
var pagerdutyReceiver = models.AlertNotification{
Id: 10,
Uid: "pagerduty UID",
OrgId: 1,
Name: "a pagerduty receiver",
Type: "pagerduty",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"httpMethod": "POST",
"severity": "critical",
"uploadImage": true
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"integrationKey": "<integrationKey>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var pagerdutyReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&pagerdutyReceiver))
var pushoverReceiver = models.AlertNotification{
Id: 11,
Uid: "pushover UID",
OrgId: 1,
Name: "a pushover receiver",
Type: "pushover",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"apiToken": "",
"autoResolve": true,
"device": "",
"expire": "",
"httpMethod": "POST",
"okPriority": "0",
"okSound": "cosmic",
"priority": "1",
"retry": "30",
"severity": "critical",
"sound": "pushover",
"uploadImage": true,
"userKey": ""
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"apiToken": "<apiToken>",
"userKey": "<userKey>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var pushoverReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&pushoverReceiver))
var sensuReceiver = models.AlertNotification{
Id: 12,
Uid: "sensu UID",
OrgId: 1,
Name: "a sensu receiver",
Type: "sensu",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"handler": "",
"httpMethod": "POST",
"severity": "critical",
"source": "",
"uploadImage": false,
"url": "http://sensu-api.local:4567/results",
"username": ""
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var sensuReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&sensuReceiver))
var sensugoReceiver = models.AlertNotification{
Id: 13,
Uid: "sensugo UID",
OrgId: 1,
Name: "a sensugo receiver",
Type: "sensugo",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"check": "",
"entity": "",
"handler": "",
"httpMethod": "POST",
"namespace": "",
"severity": "critical",
"uploadImage": false,
"url": "http://sensu-api.local:8080"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"apikey": "<apikey>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var sensugoReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&sensugoReceiver))
var slackReceiver = models.AlertNotification{
Id: 14,
Uid: "slack UID",
OrgId: 1,
Name: "a slack receiver",
Type: "slack",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"httpMethod": "POST",
"iconEmoji": "",
"iconUrl": "",
"mentionGroups": "",
"mentionUsers": "",
"recipient": "",
"severity": "critical",
"uploadImage": false,
"username": ""
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"token": "<token>",
"url": "<url>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var slackReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&slackReceiver))
var teamsReceiver = models.AlertNotification{
Id: 15,
Uid: "teams UID",
OrgId: 1,
Name: "a teams receiver",
Type: "teams",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"httpMethod": "POST",
"severity": "critical",
"uploadImage": false,
"url": "http://"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var teamsReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&teamsReceiver))
var telegramReceiver = models.AlertNotification{
Id: 16,
Uid: "telegram UID",
OrgId: 1,
Name: "a telegram receiver",
Type: "telegram",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"chatid": "12345",
"httpMethod": "POST",
"severity": "critical",
"uploadImage": false
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"bottoken": "<bottoken>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var telegramReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&telegramReceiver))
var threemaReceiver = models.AlertNotification{
Id: 17,
Uid: "threema UID",
OrgId: 1,
Name: "a threema receiver",
Type: "threema",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"gateway_id": "*3MAGWID",
"httpMethod": "POST",
"recipient_id": "YOUR3MID",
"severity": "critical",
"uploadImage": false
},`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"api_secret": "<api_secret>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var threemaDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&threemaReceiver))
var victoropsReceiver = models.AlertNotification{
Id: 18,
Uid: "victorops UID",
OrgId: 1,
Name: "a victorops receiver",
Type: "victorops",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`{
"autoResolve": true,
"httpMethod": "POST",
"severity": "critical",
"uploadImage": false,
"url": "http://"
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var victoropsReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&victoropsReceiver))
var webhookReceiver = models.AlertNotification{
Id: 19,
Uid: "webhook UID",
OrgId: 1,
Name: "a webhook receiver",
Type: "webhook",
SendReminder: false,
DisableResolveMessage: false,
Frequency: 5 * time.Minute,
IsDefault: false,
Settings: toSimpleJSON(`x{
"autoResolve": true,
"httpMethod": "POST",
"password": "",
"severity": "critical",
"uploadImage": true,
"url": "http://localhost:3010",
"username": ""
}`),
SecureSettings: securejsondata.GetEncryptedJsonData(map[string]string{
"password": "<password>",
}),
Created: time.Now().Add(-time.Hour),
Updated: time.Now().Add(-5 * time.Minute),
}
var webhookReceiverDTO = apimodels.GettableGrafanaReceiver(*dtos.NewAlertNotification(&webhookReceiver))
type AlertmanagerApiMock struct {
log log.Logger
}
func (mock AlertmanagerApiMock) RouteCreateSilence(c *models.ReqContext, body apimodels.CreateSilenceParams) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteCreateSilence: ", "Recipient", recipient)
mock.log.Info("RouteCreateSilence: ", "body", body)
return response.JSON(http.StatusAccepted, util.DynMap{"message": "silence created"})
}
func (mock AlertmanagerApiMock) RouteDeleteAlertingConfig(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteDeleteAlertingConfig: ", "Recipient", recipient)
return response.JSON(http.StatusOK, util.DynMap{"message": "config deleted"})
}
func (mock AlertmanagerApiMock) RouteDeleteSilence(c *models.ReqContext) response.Response {
silenceID := c.Params(":SilenceId")
mock.log.Info("RouteDeleteSilence: ", "SilenceId", silenceID)
recipient := c.Params(":Recipient")
mock.log.Info("RouteDeleteSilence: ", "Recipient", recipient)
return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"})
}
func (mock AlertmanagerApiMock) RouteGetAlertingConfig(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteGetAlertingConfig: ", "Recipient", recipient)
// now := time.Now()
result := apimodels.GettableUserConfig{
TemplateFiles: map[string]string{
"tmpl1": "val1",
"tmpl2": "val2",
},
AlertmanagerConfig: apimodels.GettableApiAlertingConfig{
Config: apimodels.Config{
Global: &config.GlobalConfig{},
Route: &config.Route{},
InhibitRules: []*config.InhibitRule{},
Receivers: []*config.Receiver{},
Templates: []string{},
},
Receivers: []*apimodels.GettableApiReceiver{
{
GettableGrafanaReceivers: apimodels.GettableGrafanaReceivers{
GrafanaManagedReceivers: []*apimodels.GettableGrafanaReceiver{
&alertmanagerReceiverDTO,
&dingdingReceiverDTO,
&discordReceiverDTO,
&emailReceiverDTO,
&googlechatReceiverDTOs,
&hipchatReceiverDTO,
&kafkaReceiverDTO,
&lineReceiverDTO,
&opsgenieReceiverDTO,
&pagerdutyReceiverDTO,
&pushoverReceiverDTO,
&sensuReceiverDTO,
&sensugoReceiverDTO,
&slackReceiverDTO,
&teamsReceiverDTO,
&telegramReceiverDTO,
&threemaDTO,
&victoropsReceiverDTO,
&webhookReceiverDTO,
},
},
},
},
},
}
return response.JSON(http.StatusOK, result)
}
func (mock AlertmanagerApiMock) RouteGetAMAlertGroups(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteGetAMAlertGroups: ", "Recipient", recipient)
now := time.Now()
result := apimodels.AlertGroups{
&amv2.AlertGroup{
Alerts: []*amv2.GettableAlert{
{
Annotations: amv2.LabelSet{
"annotation1-1": "value1",
"annotation1-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 1"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 1-1"),
},
{
Name: stringPtr("receiver identifier 1-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 1"},
SilencedBy: []string{"silencedBy 1"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label1-1": "value1",
"label1-2": "value2",
},
},
},
{
Annotations: amv2.LabelSet{
"annotation2-1": "value1",
"annotation2-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 2"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 2-1"),
},
{
Name: stringPtr("receiver identifier 2-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 2"},
SilencedBy: []string{"silencedBy 2"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
},
},
},
Labels: amv2.LabelSet{
"label1-1": "value1",
"label1-2": "value2",
},
Receiver: &amv2.Receiver{
Name: stringPtr("receiver identifier 2-1"),
},
},
&amv2.AlertGroup{
Alerts: []*amv2.GettableAlert{
{
Annotations: amv2.LabelSet{
"annotation2-1": "value1",
"annotation2-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 2"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 2-1"),
},
{
Name: stringPtr("receiver identifier 2-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 2"},
SilencedBy: []string{"silencedBy 2"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
},
},
},
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
Receiver: &amv2.Receiver{
Name: stringPtr("receiver identifier 2-1"),
},
},
}
return response.JSON(http.StatusOK, result)
}
func (mock AlertmanagerApiMock) RouteGetAMAlerts(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteGetAMAlerts: ", "Recipient", recipient)
now := time.Now()
result := apimodels.GettableAlerts{
&amv2.GettableAlert{
Annotations: amv2.LabelSet{
"annotation1-1": "value1",
"annotation1-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 1"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 1-1"),
},
{
Name: stringPtr("receiver identifier 1-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 1"},
SilencedBy: []string{"silencedBy 1"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label1-1": "value1",
"label1-2": "value2",
},
},
},
&amv2.GettableAlert{
Annotations: amv2.LabelSet{
"annotation2-1": "value1",
"annotation2-2": "value2",
},
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
Fingerprint: stringPtr("fingerprint 2"),
Receivers: []*amv2.Receiver{
{
Name: stringPtr("receiver identifier 2-1"),
},
{
Name: stringPtr("receiver identifier 2-2"),
},
},
StartsAt: timePtr(strfmt.DateTime(now)),
Status: &amv2.AlertStatus{
InhibitedBy: []string{"inhibitedBy 2"},
SilencedBy: []string{"silencedBy 2"},
State: stringPtr(amv2.AlertStatusStateActive),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Alert: amv2.Alert{
GeneratorURL: strfmt.URI("a URL"),
Labels: amv2.LabelSet{
"label2-1": "value1",
"label2-2": "value2",
},
},
},
}
return response.JSON(http.StatusOK, result)
}
func (mock AlertmanagerApiMock) RouteGetSilence(c *models.ReqContext) response.Response {
silenceID := c.Params(":SilenceId")
mock.log.Info("RouteGetSilence: ", "SilenceId", silenceID)
recipient := c.Params(":Recipient")
mock.log.Info("RouteGetSilence: ", "Recipient", recipient)
now := time.Now()
result := apimodels.GettableSilence{
ID: stringPtr("id"),
Status: &amv2.SilenceStatus{
State: stringPtr("active"),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Silence: amv2.Silence{
Comment: stringPtr("comment"),
CreatedBy: stringPtr("created by"),
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
StartsAt: timePtr(strfmt.DateTime(now)),
Matchers: []*amv2.Matcher{
{
IsRegex: boolPtr(false),
Name: stringPtr("name"),
Value: stringPtr("value"),
},
{
IsRegex: boolPtr(false),
Name: stringPtr("name2"),
Value: stringPtr("value2"),
},
},
},
}
return response.JSON(http.StatusOK, result)
}
func (mock AlertmanagerApiMock) RouteGetSilences(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteGetSilences: ", "Recipient", recipient)
now := time.Now()
result := apimodels.GettableSilences{
&amv2.GettableSilence{
ID: stringPtr("silence1"),
Status: &amv2.SilenceStatus{
State: stringPtr("active"),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Silence: amv2.Silence{
Comment: stringPtr("silence1 comment"),
CreatedBy: stringPtr("silence1 created by"),
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
StartsAt: timePtr(strfmt.DateTime(now)),
Matchers: []*amv2.Matcher{
{
IsRegex: boolPtr(false),
Name: stringPtr("silence1 name"),
Value: stringPtr("silence1 value"),
},
{
IsRegex: boolPtr(true),
Name: stringPtr("silence1 name2"),
Value: stringPtr("silence1 value2"),
},
},
},
},
&amv2.GettableSilence{
ID: stringPtr("silence2"),
Status: &amv2.SilenceStatus{
State: stringPtr("pending"),
},
UpdatedAt: timePtr(strfmt.DateTime(now.Add(-time.Hour))),
Silence: amv2.Silence{
Comment: stringPtr("silence2 comment"),
CreatedBy: stringPtr("silence2 created by"),
EndsAt: timePtr(strfmt.DateTime(now.Add(time.Hour))),
StartsAt: timePtr(strfmt.DateTime(now)),
Matchers: []*amv2.Matcher{
{
IsRegex: boolPtr(false),
Name: stringPtr("silence2 name"),
Value: stringPtr("silence2 value"),
},
{
IsRegex: boolPtr(true),
Name: stringPtr("silence2 name2"),
Value: stringPtr("silence2 value2"),
},
},
},
},
}
return response.JSON(http.StatusOK, result)
}
func (mock AlertmanagerApiMock) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RoutePostAlertingConfig: ", "Recipient", recipient)
mock.log.Info("RoutePostAlertingConfig: ", "body", body)
return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration created"})
}
func (mock AlertmanagerApiMock) RoutePostAMAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RoutePostAMAlerts: ", "Recipient", recipient)
mock.log.Info("RoutePostAMAlerts: ", "body", body)
return response.JSON(http.StatusOK, util.DynMap{"message": "alerts created"})
}

View File

@ -38,7 +38,7 @@ func (am *ForkedAMSvc) getService(ctx *models.ReqContext) (AlertmanagerApiServic
}
}
func (am *ForkedAMSvc) RouteCreateSilence(ctx *models.ReqContext, body apimodels.CreateSilenceParams) response.Response {
func (am *ForkedAMSvc) RouteCreateSilence(ctx *models.ReqContext, body apimodels.PostableSilence) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)

View File

@ -32,7 +32,7 @@ func NewLotexAM(proxy *AlertingProxy, log log.Logger) *LotexAM {
}
}
func (am *LotexAM) RouteCreateSilence(ctx *models.ReqContext, silenceBody apimodels.CreateSilenceParams) response.Response {
func (am *LotexAM) RouteCreateSilence(ctx *models.ReqContext, silenceBody apimodels.PostableSilence) response.Response {
blob, err := json.Marshal(silenceBody)
if err != nil {
return response.Error(500, "Failed marshal silence", err)

View File

@ -10,23 +10,109 @@ GET http://admin:admin@localhost:3000/alertmanager/{{lokiDatasourceID}}/config/a
GET http://admin:admin@localhost:3000/alertmanager/{{prometheusDatasourceID}}/config/api/v1/alerts
###
# grafana requests
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
###
DELETE http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
###
# create AM configuration
POST http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
content-type: application/json
< ./post-user-config.json
###
# get latest AM configuration
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
content-type: application/json
###
# delete AM configuration
DELETE http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
###
# create AM alerts
POST http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/alerts
content-type: application/json
###
# get silences - no silences
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences?Filter=foo="bar"&Filter=bar="foo"
###
# create silence
POST http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences
content-type: application/json
< ./post-silence-data.json
{
"comment": "string",
"createdBy": "string",
"endsAt": "2023-03-31T14:17:04.419Z",
"matchers": [
{
"isRegex": true,
"name": "string",
"value": "string"
}
],
"startsAt": "2021-03-31T13:17:04.419Z"
}
###
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences
# update silence - does not exist
POST http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences
content-type: application/json
{
"id": "something",
"comment": "string",
"createdBy": "string",
"endsAt": "2023-03-31T14:17:04.419Z",
"matchers": [
{
"isRegex": true,
"name": "string",
"value": "string"
}
],
"startsAt": "2021-03-31T13:17:04.419Z"
}
###
# create silence - bad paylaad - start time must be before end time
POST http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences
content-type: application/json
{
"comment": "string",
"createdBy": "string",
"endsAt": "2019-03-31T14:17:04.419Z",
"matchers": [
{
"isRegex": true,
"name": "string",
"value": "string"
}
],
"startsAt": "2021-03-31T13:17:04.419Z"
}
###
# get silences
# @name getSilences
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences
###
@silenceID = {{getSilences.response.body.$.[0].id}}
###
# get silence
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silence/{{silenceID}}
###
# get silence - unknown
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silence/unknown
###
# delete silence
DELETE http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silence/{{silenceID}}
###
# delete silence - unknown
DELETE http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silence/unknown

View File

@ -8,11 +8,13 @@
"resolve_timeout": "1s",
"smtp_smarthost": ""
},
"route": {},
"route": {
"receiver": "grafana_managed"
},
"templates": [],
"receivers": [
{
"name": "",
"name": "grafana_managed",
"grafana_managed_receiver_configs": [
{
"uid": "alertmanager UID",

View File

@ -35,10 +35,6 @@ func stringPtr(s string) *string {
return &s
}
func boolPtr(b bool) *bool {
return &b
}
func backendType(ctx *models.ReqContext, cache datasources.CacheService) (apimodels.Backend, error) {
recipient := ctx.Params("Recipient")
if recipient == apimodels.GrafanaBackend.String() {

View File

@ -2,6 +2,8 @@ package models
import "time"
const AlertConfigurationVersion = 1
// AlertConfiguration represents a single version of the Alerting Engine Configuration.
type AlertConfiguration struct {
ID int64 `xorm:"pk autoincr 'id'"`
@ -13,7 +15,22 @@ type AlertConfiguration struct {
// GetLatestAlertmanagerConfigurationQuery is the query to get the latest alertmanager configuration.
type GetLatestAlertmanagerConfigurationQuery struct {
Result *AlertConfiguration
}
// GetAlertmanagerConfigurationQuery is the query to get the latest alertmanager configuration.
type GetAlertmanagerConfigurationQuery struct {
ID int64
Result *AlertConfiguration
}
// SaveAlertmanagerConfigurationCmd is the command to save an alertmanager configuration.
type SaveAlertmanagerConfigurationCmd struct {
AlertmanagerConfiguration string
ConfigurationVersion string
}
type DeleteAlertmanagerConfigurationCmd struct {
ID int64
}

View File

@ -0,0 +1,119 @@
package models
import (
"encoding/json"
"errors"
"fmt"
"github.com/go-openapi/strfmt"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
)
var (
// ErrSilenceNotFound is an error for an unknown silence.
ErrSilenceNotFound = fmt.Errorf("could not find silence")
// ErrSilenceFailedGenerateUniqueUID is an error for failure to generate silence UID
ErrSilenceFailedGenerateUniqueUID = errors.New("failed to generate silence UID")
)
type SilenceSettings amv2.GettableSilence
type SilenceStatus amv2.SilenceStatus
// FromDB loads silence status stored in the database.
// FromDB is part of the xorm Conversion interface.
func (st *SilenceStatus) FromDB(b []byte) error {
str := string(b)
*st = SilenceStatus{State: &str}
return nil
}
// ToDB serializes silence status to be stored in the database.
// ToDB is part of the xorm Conversion interface.
func (st *SilenceStatus) ToDB() ([]byte, error) {
return []byte(*st.State), nil
}
type Matchers amv2.Matchers
// FromDB loads matchers stored in the database.
// FromDB is part of the xorm Conversion interface.
func (m *Matchers) FromDB(b []byte) error {
err := json.Unmarshal(b, &m)
if err != nil {
return fmt.Errorf("failed to convert matchers from database: %w", err)
}
return nil
}
// ToDB serializes matchers to be stored in the database.
// ToDB is part of the xorm Conversion interface.
func (m *Matchers) ToDB() ([]byte, error) {
blobMatchers, err := json.Marshal(m)
if err != nil {
return nil, fmt.Errorf("failed to convert matchers to send to the database: %w", err)
}
return blobMatchers, nil
}
type Silence struct {
ID int64 `xorm:"pk autoincr 'id'"`
OrgID int64 `xorm:"org_id" json:"orgId"`
UID string `xorm:"uid" json:"uid"`
Status SilenceStatus `json:"status"`
UpdatedAt strfmt.DateTime `json:"updatedAt"`
Comment string `json:"comment"`
CreatedBy string `json:"createdBy"`
EndsAt strfmt.DateTime `json:"endsAt"`
Matchers Matchers `json:"matchers"`
StartsAt strfmt.DateTime `json:"startsAt"`
}
func (s Silence) ToGettableSilence() amv2.GettableSilence {
gettableSilence := amv2.GettableSilence{
ID: &s.UID,
Status: &amv2.SilenceStatus{State: s.Status.State},
UpdatedAt: &s.UpdatedAt,
}
gettableSilence.Comment = &s.Comment
gettableSilence.CreatedBy = &s.CreatedBy
gettableSilence.EndsAt = &s.EndsAt
gettableSilence.Matchers = amv2.Matchers(s.Matchers)
gettableSilence.StartsAt = &s.StartsAt
return gettableSilence
}
type SaveSilenceCommand struct {
amv2.Silence
UID string
OrgID int64
}
type DeleteSilenceByUIDCommand struct {
UID string
OrgID int64
}
type DeleteSilenceByIDCommand struct {
ID int64
}
type GetSilenceByUIDQuery struct {
UID string
OrgID int64
Result *Silence
}
type GetSilenceByIDQuery struct {
ID int64
Result *Silence
}
type GetSilencesQuery struct {
OrgID int64
Result []*Silence
}

View File

@ -81,6 +81,7 @@ func (ng *AlertNG) Init() error {
Schedule: ng.schedule,
DataProxy: ng.DataProxy,
Store: store,
AlertingStore: store,
Alertmanager: ng.Alertmanager,
}
api.RegisterAPIEndpoints()
@ -99,7 +100,6 @@ func (ng *AlertNG) IsDisabled() bool {
if ng.Cfg == nil {
return true
}
// Check also about expressions?
return !ng.Cfg.IsNgAlertEnabled()
}
@ -109,8 +109,11 @@ func (ng *AlertNG) AddMigration(mg *migrator.Migrator) {
if ng.IsDisabled() {
return
}
addAlertDefinitionMigrations(mg)
addAlertDefinitionVersionMigrations(mg)
store.AddAlertDefinitionMigrations(mg, defaultIntervalSeconds)
store.AddAlertDefinitionVersionMigrations(mg)
// Create alert_instance table
alertInstanceMigration(mg)
store.AlertInstanceMigration(mg)
// Create silence table
store.SilenceMigration(mg)
}

View File

@ -2,10 +2,15 @@ package notifier
import (
"context"
"fmt"
"path/filepath"
"sync"
"time"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/models"
gokit_log "github.com/go-kit/kit/log"
"github.com/grafana/alerting-api/pkg/api"
"github.com/pkg/errors"
@ -20,7 +25,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/ngalert/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/sqlstore"
@ -61,7 +66,10 @@ func init() {
}
func (am *Alertmanager) IsDisabled() bool {
return !setting.AlertingEnabled || !setting.ExecuteAlerts
if am.Settings == nil {
return true
}
return !am.Settings.IsNgAlertEnabled()
}
func (am *Alertmanager) Init() (err error) {
@ -146,7 +154,7 @@ func (am *Alertmanager) SyncAndApplyConfigFromDatabase() error {
func (am *Alertmanager) getConfigFromDatabase() (*api.PostableUserConfig, error) {
// First, let's get the configuration we need from the database.
q := &models.GetLatestAlertmanagerConfigurationQuery{}
q := &ngmodels.GetLatestAlertmanagerConfigurationQuery{}
if err := am.Store.GetLatestAlertmanagerConfiguration(q); err != nil {
return nil, err
}
@ -233,7 +241,22 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *api.PostableApiRecei
for i, r := range receiver.GrafanaManagedReceivers {
switch r.Type {
case "email":
n, err := channels.NewEmailNotifier(r.Result)
frequency, err := time.ParseDuration(r.Frequency)
if err != nil {
return nil, fmt.Errorf("unable to parse receiver frequency %s, %w", r.Frequency, err)
}
notification := models.AlertNotification{
Uid: r.Uid,
Name: r.Name,
Type: r.Type,
IsDefault: r.IsDefault,
SendReminder: r.SendReminder,
DisableResolveMessage: r.DisableResolveMessage,
Frequency: frequency,
Settings: r.Settings,
SecureSettings: securejsondata.GetEncryptedJsonData(r.SecureSettings),
}
n, err := channels.NewEmailNotifier(&notification)
if err != nil {
return nil, err
}

View File

@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
func PersistTemplates(cfg *api.PostableUserConfig, path string) ([]string, bool, error) {
@ -78,7 +78,7 @@ func PersistTemplates(cfg *api.PostableUserConfig, path string) ([]string, bool,
func Load(rawConfig string) (*api.PostableUserConfig, error) {
cfg := &api.PostableUserConfig{}
if err := yaml.UnmarshalStrict([]byte(rawConfig), cfg); err != nil {
if err := yaml.Unmarshal([]byte(rawConfig), cfg); err != nil {
return nil, errors.Wrap(err, "unable to parse Alertmanager configuration")
}

View File

@ -13,22 +13,71 @@ var (
ErrNoAlertmanagerConfiguration = fmt.Errorf("could not find an Alertmanager configuration")
)
func getAlertmanagerConfigurationByID(sess *sqlstore.DBSession, id int64) (*models.AlertConfiguration, error) {
c := &models.AlertConfiguration{}
has, err := sess.ID(id).Get(c)
if err != nil {
return nil, err
}
if !has {
return nil, ErrNoAlertmanagerConfiguration
}
return c, nil
}
func getLatestAlertmanagerConfiguration(sess *sqlstore.DBSession) (*models.AlertConfiguration, error) {
c := &models.AlertConfiguration{}
// The ID is already an auto incremental column, using the ID as an order should guarantee the latest.
ok, err := sess.Desc("id").Limit(1).Get(c)
if err != nil {
return nil, err
}
if !ok {
return nil, ErrNoAlertmanagerConfiguration
}
return c, nil
}
// GetLatestAlertmanagerConfiguration returns the lastest version of the alertmanager configuration.
// It returns ErrNoAlertmanagerConfiguration if no configuration is found.
func (st DBstore) GetLatestAlertmanagerConfiguration(query *models.GetLatestAlertmanagerConfigurationQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
c := &models.AlertConfiguration{}
// The ID is already an auto incremental column, using the ID as an order should guarantee the latest.
ok, err := sess.Desc("id").Limit(1).Get(c)
c, err := getLatestAlertmanagerConfiguration(sess)
if err != nil {
return err
}
if !ok {
return ErrNoAlertmanagerConfiguration
}
query.Result = c
return nil
})
}
// GetAlertmanagerConfiguration returns the alertmanager configuration identified by the query.
// It returns ErrNoAlertmanagerConfiguration if no such configuration is found.
func (st DBstore) GetAlertmanagerConfiguration(query *models.GetAlertmanagerConfigurationQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
c, err := getAlertmanagerConfigurationByID(sess, query.ID)
if err != nil {
return err
}
query.Result = c
return nil
})
}
// SaveAlertmanagerConfiguration creates an alertmanager configuration.
func (st DBstore) SaveAlertmanagerConfiguration(cmd *models.SaveAlertmanagerConfigurationCmd) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
config := models.AlertConfiguration{
AlertmanagerConfiguration: cmd.AlertmanagerConfiguration,
ConfigurationVersion: cmd.ConfigurationVersion,
}
if _, err := sess.Insert(config); err != nil {
return err
}
return nil
})
}

View File

@ -26,20 +26,28 @@ var ErrEmptyTitleError = errors.New("title is empty")
type Store interface {
DeleteAlertDefinitionByUID(*models.DeleteAlertDefinitionByUIDCommand) error
GetAlertDefinitionByUID(*models.GetAlertDefinitionByUIDQuery) error
GetAlertDefinitions(query *models.ListAlertDefinitionsQuery) error
GetOrgAlertDefinitions(query *models.ListAlertDefinitionsQuery) error
GetAlertDefinitions(*models.ListAlertDefinitionsQuery) error
GetOrgAlertDefinitions(*models.ListAlertDefinitionsQuery) error
SaveAlertDefinition(*models.SaveAlertDefinitionCommand) error
UpdateAlertDefinition(*models.UpdateAlertDefinitionCommand) error
GetAlertInstance(*models.GetAlertInstanceQuery) error
ListAlertInstances(cmd *models.ListAlertInstancesQuery) error
SaveAlertInstance(cmd *models.SaveAlertInstanceCommand) error
ListAlertInstances(*models.ListAlertInstancesQuery) error
SaveAlertInstance(*models.SaveAlertInstanceCommand) error
ValidateAlertDefinition(*models.AlertDefinition, bool) error
UpdateAlertDefinitionPaused(*models.UpdateAlertDefinitionPausedCommand) error
}
// AlertingStore is the database interface used by the Alertmanager service.
type AlertingStore interface {
GetLatestAlertmanagerConfiguration(cmd *models.GetLatestAlertmanagerConfigurationQuery) error
GetLatestAlertmanagerConfiguration(*models.GetLatestAlertmanagerConfigurationQuery) error
GetAlertmanagerConfiguration(*models.GetAlertmanagerConfigurationQuery) error
SaveAlertmanagerConfiguration(*models.SaveAlertmanagerConfigurationCmd) error
GetOrgSilences(*models.GetSilencesQuery) error
GetSilenceByUID(*models.GetSilenceByUIDQuery) error
GetSilenceByID(*models.GetSilenceByIDQuery) error
SaveSilence(*models.SaveSilenceCommand) error
DeleteSilenceByUID(*models.DeleteSilenceByUIDCommand) error
DeleteSilenceByID(*models.DeleteSilenceByIDCommand) error
}
// DBstore stores the alert definitions and instances in the database.

View File

@ -1,4 +1,4 @@
package ngalert
package store
import (
"fmt"
@ -6,7 +6,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
func addAlertDefinitionMigrations(mg *migrator.Migrator) {
func AddAlertDefinitionMigrations(mg *migrator.Migrator, defaultIntervalSeconds int64) {
mg.AddMigration("delete alert_definition table", migrator.NewDropTableMigration("alert_definition"))
alertDefinition := migrator.Table{
@ -52,7 +52,7 @@ func addAlertDefinitionMigrations(mg *migrator.Migrator) {
}))
}
func addAlertDefinitionVersionMigrations(mg *migrator.Migrator) {
func AddAlertDefinitionVersionMigrations(mg *migrator.Migrator) {
mg.AddMigration("delete alert_definition_version table", migrator.NewDropTableMigration("alert_definition_version"))
alertDefinitionVersion := migrator.Table{
@ -83,7 +83,7 @@ func addAlertDefinitionVersionMigrations(mg *migrator.Migrator) {
Mysql("ALTER TABLE alert_definition_version MODIFY data MEDIUMTEXT;"))
}
func alertInstanceMigration(mg *migrator.Migrator) {
func AlertInstanceMigration(mg *migrator.Migrator) {
alertInstance := migrator.Table{
Name: "alert_instance",
Columns: []*migrator.Column{
@ -107,3 +107,27 @@ func alertInstanceMigration(mg *migrator.Migrator) {
mg.AddMigration("add index in alert_instance table on def_org_id, def_uid and current_state columns", migrator.NewAddIndexMigration(alertInstance, alertInstance.Indices[0]))
mg.AddMigration("add index in alert_instance table on def_org_id, current_state columns", migrator.NewAddIndexMigration(alertInstance, alertInstance.Indices[1]))
}
func SilenceMigration(mg *migrator.Migrator) {
silence := migrator.Table{
Name: "silence",
Columns: []*migrator.Column{
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "uid", Type: migrator.DB_NVarchar, Length: 190, Nullable: false, Default: "0"},
{Name: "comment", Type: migrator.DB_NVarchar, Length: 190, Nullable: true},
{Name: "created_by", Type: migrator.DB_NVarchar, Length: 190, Nullable: true},
{Name: "matchers", Type: migrator.DB_Text, Nullable: false},
{Name: "ends_at", Type: migrator.DB_DateTime, Nullable: false},
{Name: "starts_at", Type: migrator.DB_DateTime, Nullable: false},
{Name: "updated_at", Type: migrator.DB_DateTime, Nullable: true},
{Name: "status", Type: migrator.DB_NVarchar, Length: 8, Nullable: false},
},
Indices: []*migrator.Index{
{Cols: []string{"org_id", "uid"}, Type: migrator.IndexType},
},
}
mg.AddMigration("create_silence_table", migrator.NewAddTableMigration(silence))
mg.AddMigration("add unique index in silence on org_id and uid columns", migrator.NewAddIndexMigration(silence, silence.Indices[0]))
}

View File

@ -0,0 +1,165 @@
package store
import (
"context"
"errors"
"fmt"
"github.com/go-openapi/strfmt"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/util"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
)
func getSilenceByUID(sess *sqlstore.DBSession, silenceUID string, orgID int64) (*models.Silence, error) {
silence := &models.Silence{
OrgID: orgID,
UID: silenceUID,
}
has, err := sess.Get(silence)
if err != nil {
return nil, err
}
if !has {
return nil, models.ErrSilenceNotFound
}
return silence, nil
}
func getSilenceByID(sess *sqlstore.DBSession, id int64) (*models.Silence, error) {
silence := &models.Silence{}
has, err := sess.ID(id).Get(silence)
if err != nil {
return nil, err
}
if !has {
return nil, ErrNoAlertmanagerConfiguration
}
return silence, nil
}
func (st DBstore) GetOrgSilences(query *models.GetSilencesQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
silences := make([]*models.Silence, 0)
q := "SELECT * FROM silence WHERE org_id = ?"
if err := sess.SQL(q, query.OrgID).Find(&silences); err != nil {
return err
}
query.Result = silences
return nil
})
}
func (st DBstore) GetSilenceByUID(query *models.GetSilenceByUIDQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
silence, err := getSilenceByUID(sess, query.UID, query.OrgID)
if err != nil {
return err
}
query.Result = silence
return nil
})
}
func (st DBstore) GetSilenceByID(query *models.GetSilenceByIDQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
silence, err := getSilenceByID(sess, query.ID)
if err != nil {
return err
}
query.Result = silence
return nil
})
}
func (st DBstore) DeleteSilenceByUID(cmd *models.DeleteSilenceByUIDCommand) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
_, err := getSilenceByUID(sess, cmd.UID, cmd.OrgID)
if err != nil && errors.Is(err, models.ErrSilenceNotFound) {
return err
}
_, err = sess.Exec("DELETE FROM silence WHERE uid = ? AND org_id = ?", cmd.UID, cmd.OrgID)
if err != nil {
return err
}
return nil
})
}
func (st DBstore) DeleteSilenceByID(cmd *models.DeleteSilenceByIDCommand) error {
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
_, err := getSilenceByID(sess, cmd.ID)
if err != nil && errors.Is(err, models.ErrSilenceNotFound) {
return err
}
_, err = sess.Exec("DELETE FROM silence WHERE id ?", cmd.ID)
if err != nil {
return err
}
return nil
})
}
func (st DBstore) SaveSilence(cmd *models.SaveSilenceCommand) error {
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
var firstSeen bool
existingSilence, err := getSilenceByUID(sess, cmd.UID, cmd.OrgID)
if err != nil {
if !errors.Is(err, models.ErrSilenceNotFound) {
return err
}
firstSeen = true
}
statusPending := amv2.SilenceStatusStatePending
updatedAt := strfmt.DateTime(TimeNow())
silenceModel := models.Silence{
OrgID: cmd.OrgID,
UID: cmd.UID,
Status: models.SilenceStatus{State: &statusPending},
UpdatedAt: updatedAt,
Comment: *cmd.Comment,
CreatedBy: *cmd.CreatedBy,
EndsAt: *cmd.EndsAt,
Matchers: models.Matchers(cmd.Matchers),
StartsAt: *cmd.StartsAt,
}
switch firstSeen {
case true:
if _, err := sess.Insert(&silenceModel); err != nil {
return fmt.Errorf("failed to insert silence: %w", err)
}
default:
if _, err := sess.ID(existingSilence.ID).Update(&silenceModel); err != nil {
return fmt.Errorf("failed to update silence: %w", err)
}
}
return nil
})
}
func GenerateNewSilenceUID(sess *sqlstore.DBSession, orgID int64) (string, error) {
for i := 0; i < 3; i++ {
uid := util.GenerateShortUID()
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&models.Silence{})
if err != nil {
return "", err
}
if !exists {
return uid, nil
}
}
return "", models.ErrSilenceFailedGenerateUniqueUID
}