Alerting: Introduce the silencing interface (#32517)

* Alerting: Introduce the silencing interface

The operations introduced are:

- Listing silences
- Retrieving an specific silence
- Deleting a silence
- Creating a silence

Signed-off-by: Josue Abreu <josue@grafana.com>

* Add a comment to listing silences

* Update to upstream alertmanager

* Remove copied code from the Alertmanager
pull/32174/head
gotjosh 4 years ago committed by GitHub
parent 230f95dc47
commit 433f6b91d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      go.mod
  2. 13
      go.sum
  3. 62
      pkg/services/ngalert/api/api_alertmanager_base.go
  4. 2
      pkg/services/ngalert/api/api_alertmanager_mock.go
  5. 2
      pkg/services/ngalert/api/forked_am.go
  6. 2
      pkg/services/ngalert/api/lotex_am.go
  7. 133
      pkg/services/ngalert/notifier/alertmanager.go
  8. 121
      pkg/services/ngalert/notifier/silences.go

@ -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-20210323194814-03a29a4c4c27
github.com/grafana/alerting-api v0.0.0-20210330162237-0b5408c529a8
github.com/grafana/grafana-aws-sdk v0.3.0
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
github.com/grafana/grafana-plugin-sdk-go v0.89.0
@ -62,7 +62,7 @@ require (
github.com/opentracing/opentracing-go v1.2.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/alertmanager v0.21.1-0.20210324070758-10757eb5fb78
github.com/prometheus/alertmanager v0.21.1-0.20210331075806-bc7b16d61afd
github.com/prometheus/client_golang v1.10.0
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.19.0

@ -362,6 +362,7 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-plugins-helpers v0.0.0-20181025120712-1e6269c305b8/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
@ -795,14 +796,12 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
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-20210323194814-03a29a4c4c27 h1:DuyuEAHJeI+CMxIyzCVhmHcIeK+sjqberhDUfrgd3PY=
github.com/grafana/alerting-api v0.0.0-20210323194814-03a29a4c4c27/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
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/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=
github.com/grafana/grafana-aws-sdk v0.1.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
github.com/grafana/grafana-aws-sdk v0.2.0 h1:UTBBYwye+ad5YUIlwN7TGxLdz1wXN3Ezhl0pseDGRVA=
github.com/grafana/grafana-aws-sdk v0.2.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
github.com/grafana/grafana-aws-sdk v0.3.0 h1:UT3rIXQFeAh0OaRJT7dUQojYaSjbI9RwOtMacaerv8I=
github.com/grafana/grafana-aws-sdk v0.3.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag=
@ -968,6 +967,7 @@ github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxy
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
@ -1336,8 +1336,8 @@ github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2a
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=
github.com/prometheus/alertmanager v0.21.1-0.20210324070758-10757eb5fb78 h1:au9OzjUv3GWdL4s98I84hx38oAs+xxxpv/9n2Xuh6n0=
github.com/prometheus/alertmanager v0.21.1-0.20210324070758-10757eb5fb78/go.mod h1:g6wbBgNXmelfXjJhLLl5NIJDpejM5oEjiSKDsqnTzio=
github.com/prometheus/alertmanager v0.21.1-0.20210331075806-bc7b16d61afd h1:lu8HKBn7BmowoO79Ypzxn5EN6fHrAbJ6his5dRKDUIY=
github.com/prometheus/alertmanager v0.21.1-0.20210331075806-bc7b16d61afd/go.mod h1:g6wbBgNXmelfXjJhLLl5NIJDpejM5oEjiSKDsqnTzio=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
@ -1440,6 +1440,7 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russellhaering/goxmldsig v1.1.0 h1:lK/zeJie2sqG52ZAlPNn1oBBqsIsEKypUUBGpYYF6lk=
github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=

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

@ -523,7 +523,7 @@ type AlertmanagerApiMock struct {
log log.Logger
}
func (mock AlertmanagerApiMock) RouteCreateSilence(c *models.ReqContext, body apimodels.SilenceBody) response.Response {
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)

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

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

@ -3,7 +3,6 @@ package notifier
import (
"context"
"path/filepath"
"sort"
"sync"
"time"
@ -14,9 +13,7 @@ import (
"github.com/prometheus/alertmanager/nflog"
"github.com/prometheus/alertmanager/nflog/nflogpb"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/alertmanager/silence"
"github.com/prometheus/alertmanager/silence/silencepb"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/client_golang/prometheus"
@ -33,6 +30,8 @@ import (
const (
workingDir = "alerting"
// How long should we keep silences and notification entries on-disk after they've served their purpose.
retentionNotificationsAndSilences = 5 * 24 * time.Hour
)
type Alertmanager struct {
@ -74,15 +73,15 @@ func (am *Alertmanager) Init() (err error) {
am.Store = store.DBstore{SQLStore: am.SQLStore}
am.notificationLog, err = nflog.New(
nflog.WithRetention(time.Hour*24), //TODO: This is a setting.
nflog.WithSnapshot(filepath.Join("dir", "notifications")), //TODO: This should be a setting
nflog.WithRetention(retentionNotificationsAndSilences),
nflog.WithSnapshot(filepath.Join(am.WorkingDirPath(), "notifications")),
)
if err != nil {
return errors.Wrap(err, "unable to initialize the notification log component of alerting")
}
am.silences, err = silence.New(silence.Options{
SnapshotFile: filepath.Join("dir", "silences"), //TODO: This is a setting
Retention: time.Hour * 24, //TODO: This is also a setting
SnapshotFile: filepath.Join(am.WorkingDirPath(), "silences"),
Retention: retentionNotificationsAndSilences,
})
if err != nil {
return errors.Wrap(err, "unable to initialize the silencing component of alerting")
@ -251,66 +250,6 @@ func (am *Alertmanager) PutAlerts(alerts ...*PostableAlert) error {
return am.alerts.PutPostableAlert(alerts...)
}
func (am *Alertmanager) ListSilences(matchers []*labels.Matcher) ([]types.Silence, error) {
pbsilences, _, err := am.silences.Query()
if err != nil {
return nil, errors.Wrap(err, "unable to query for the list of silences")
}
r := []types.Silence{}
for _, pbs := range pbsilences {
s, err := silenceFromProto(pbs)
if err != nil {
return nil, errors.Wrap(err, "unable to marshal silence")
}
sms := make(map[string]string)
for _, m := range s.Matchers {
sms[m.Name] = m.Value
}
if !matchFilterLabels(matchers, sms) {
continue
}
r = append(r, *s)
}
var active, pending, expired []types.Silence
for _, s := range r {
switch s.Status.State {
case types.SilenceStateActive:
active = append(active, s)
case types.SilenceStatePending:
pending = append(pending, s)
case types.SilenceStateExpired:
expired = append(expired, s)
}
}
sort.Slice(active, func(i int, j int) bool {
return active[i].EndsAt.Before(active[j].EndsAt)
})
sort.Slice(pending, func(i int, j int) bool {
return pending[i].EndsAt.Before(pending[j].EndsAt)
})
sort.Slice(expired, func(i int, j int) bool {
return expired[i].EndsAt.After(expired[j].EndsAt)
})
// Initialize silences explicitly to an empty list (instead of nil)
// So that it does not get converted to "null" in JSON.
silences := []types.Silence{}
silences = append(silences, active...)
silences = append(silences, pending...)
silences = append(silences, expired...)
return silences, nil
}
func (am *Alertmanager) GetSilence(silence *types.Silence) {}
func (am *Alertmanager) CreateSilence(silence *types.Silence) {}
func (am *Alertmanager) DeleteSilence(silence *types.Silence) {}
// createReceiverStage creates a pipeline of stages for a receiver.
func (am *Alertmanager) createReceiverStage(name string, integrations []notify.Integration, wait func() time.Duration, notificationLog notify.NotificationLog) notify.Stage {
var fs notify.FanoutStage
@ -343,63 +282,3 @@ func timeoutFunc(d time.Duration) time.Duration {
}
return d + waitFunc()
}
// copied from the Alertmanager
func silenceFromProto(s *silencepb.Silence) (*types.Silence, error) {
sil := &types.Silence{
ID: s.Id,
StartsAt: s.StartsAt,
EndsAt: s.EndsAt,
UpdatedAt: s.UpdatedAt,
Status: types.SilenceStatus{
State: types.CalcSilenceState(s.StartsAt, s.EndsAt),
},
Comment: s.Comment,
CreatedBy: s.CreatedBy,
}
for _, m := range s.Matchers {
var t labels.MatchType
switch m.Type {
case silencepb.Matcher_EQUAL:
t = labels.MatchEqual
case silencepb.Matcher_REGEXP:
t = labels.MatchRegexp
case silencepb.Matcher_NOT_EQUAL:
t = labels.MatchNotEqual
case silencepb.Matcher_NOT_REGEXP:
t = labels.MatchNotRegexp
}
matcher, err := labels.NewMatcher(t, m.Name, m.Pattern)
if err != nil {
return nil, err
}
sil.Matchers = append(sil.Matchers, matcher)
}
return sil, nil
}
func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool {
for _, m := range matchers {
v, prs := sms[m.Name]
switch m.Type {
case labels.MatchNotRegexp, labels.MatchNotEqual:
if m.Value == "" && prs {
continue
}
if !m.Matches(v) {
return false
}
default:
if m.Value == "" && !prs {
continue
}
if !m.Matches(v) {
return false
}
}
}
return true
}

@ -1 +1,122 @@
package notifier
import (
"fmt"
"time"
apimodels "github.com/grafana/alerting-api/pkg/api"
"github.com/pkg/errors"
v2 "github.com/prometheus/alertmanager/api/v2"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/alertmanager/silence"
)
var (
ErrGetSilencesInternal = errors.New("unable to retrieve silence(s) due to an internal error")
ErrDeleteSilenceInternal = errors.New("unable to delete silence due to an internal error")
ErrCreateSilenceBadPayload = errors.New("unable to create silence")
ErrListSilencesBadPayload = errors.New("unable to list silences")
ErrSilenceNotFound = silence.ErrNotFound
)
// ListSilences retrieves a list of stored silences. It supports a set of labels as filters.
func (am *Alertmanager) ListSilences(filters []string) (apimodels.GettableSilences, error) {
matchers := []*labels.Matcher{}
for _, matcherString := range filters {
matcher, err := labels.ParseMatcher(matcherString)
if err != nil {
am.logger.Error("failed to parse matcher", "err", err, "matcher", matcherString)
return nil, errors.Wrap(ErrListSilencesBadPayload, err.Error())
}
matchers = append(matchers, matcher)
}
psils, _, err := am.silences.Query()
if err != nil {
am.logger.Error(ErrGetSilencesInternal.Error(), "err", err)
return nil, errors.Wrap(ErrGetSilencesInternal, err.Error())
}
sils := apimodels.GettableSilences{}
for _, ps := range psils {
if !v2.CheckSilenceMatchesFilterLabels(ps, matchers) {
continue
}
silence, err := v2.GettableSilenceFromProto(ps)
if err != nil {
am.logger.Error("unmarshaling from protobuf failed", "err", err)
return apimodels.GettableSilences{}, errors.Wrap(ErrGetSilencesInternal, fmt.Sprintf("failed to convert internal silence to API silence: %v", err.Error()))
}
sils = append(sils, &silence)
}
v2.SortSilences(sils)
return sils, nil
}
// GetSilence retrieves a silence by the provided silenceID. It returns ErrSilenceNotFound if the silence is not present.
func (am *Alertmanager) GetSilence(silenceID string) (apimodels.GettableSilence, error) {
sils, _, err := am.silences.Query(silence.QIDs(silenceID))
if err != nil {
return apimodels.GettableSilence{}, errors.Wrap(ErrGetSilencesInternal, err.Error())
}
if len(sils) == 0 {
am.logger.Error("failed to find silence", "err", err, "id", sils)
return apimodels.GettableSilence{}, ErrSilenceNotFound
}
sil, err := v2.GettableSilenceFromProto(sils[0])
if err != nil {
am.logger.Error("unmarshaling from protobuf failed", "err", err)
return apimodels.GettableSilence{}, errors.Wrap(ErrGetSilencesInternal, fmt.Sprintf("failed to convert internal silence to API silence: %v", err.Error()))
}
return sil, nil
}
// CreateSilence persists the provided silence and returns the silence ID if successful.
func (am *Alertmanager) CreateSilence(ps *apimodels.PostableSilence) (string, error) {
sil, err := v2.PostableSilenceToProto(ps)
if err != nil {
am.logger.Error("marshaling to protobuf failed", "err", err)
return "", errors.Wrap(ErrCreateSilenceBadPayload, fmt.Sprintf("failed to convert API silence to internal silence: %v", err.Error()))
}
if sil.StartsAt.After(sil.EndsAt) || sil.StartsAt.Equal(sil.EndsAt) {
msg := "start time must be before end time"
am.logger.Error(msg, "err", "starts_at", sil.StartsAt, "ends_at", sil.EndsAt)
return "", errors.Wrap(ErrCreateSilenceBadPayload, msg)
}
if sil.EndsAt.Before(time.Now()) {
msg := "end time can't be in the past"
am.logger.Error(msg, "ends_at", sil.EndsAt)
return "", errors.Wrap(ErrCreateSilenceBadPayload, msg)
}
silenceID, err := am.silences.Set(sil)
if err != nil {
am.logger.Error("msg", "unable to save silence", "err", err)
if errors.Is(err, silence.ErrNotFound) {
return "", ErrSilenceNotFound
}
return "", errors.Wrap(ErrCreateSilenceBadPayload, fmt.Sprintf("unable to save silence: %v", err.Error()))
}
return silenceID, nil
}
// DeleteSilence looks for and expires the silence by the provided silenceID. It returns ErrSilenceNotFound if the silence is not present.
func (am *Alertmanager) DeleteSilence(silenceID string) error {
if err := am.silences.Expire(silenceID); err != nil {
if errors.Is(err, silence.ErrNotFound) {
return ErrSilenceNotFound
}
return errors.Wrap(ErrDeleteSilenceInternal, err.Error())
}
return nil
}

Loading…
Cancel
Save