@@ -13,14 +13,16 @@ import (
|
||||
|
||||
"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/notifications"
|
||||
)
|
||||
|
||||
type SensuGoNotifier struct {
|
||||
*Base
|
||||
log log.Logger
|
||||
ns notifications.WebhookSender
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
images ImageStore
|
||||
ns notifications.WebhookSender
|
||||
tmpl *template.Template
|
||||
|
||||
URL string
|
||||
Entity string
|
||||
@@ -50,7 +52,7 @@ func SensuGoFactory(fc FactoryConfig) (NotificationChannel, error) {
|
||||
Cfg: *fc.Config,
|
||||
}
|
||||
}
|
||||
return NewSensuGoNotifier(cfg, fc.NotificationService, fc.Template), nil
|
||||
return NewSensuGoNotifier(cfg, fc.ImageStore, fc.NotificationService, fc.Template), nil
|
||||
}
|
||||
|
||||
func NewSensuGoConfig(config *NotificationChannelConfig, decryptFunc GetDecryptedValueFn) (*SensuGoConfig, error) {
|
||||
@@ -75,7 +77,7 @@ func NewSensuGoConfig(config *NotificationChannelConfig, decryptFunc GetDecrypte
|
||||
}
|
||||
|
||||
// NewSensuGoNotifier is the constructor for the SensuGo notifier
|
||||
func NewSensuGoNotifier(config *SensuGoConfig, ns notifications.WebhookSender, t *template.Template) *SensuGoNotifier {
|
||||
func NewSensuGoNotifier(config *SensuGoConfig, images ImageStore, ns notifications.WebhookSender, t *template.Template) *SensuGoNotifier {
|
||||
return &SensuGoNotifier{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: config.UID,
|
||||
@@ -93,6 +95,7 @@ func NewSensuGoNotifier(config *SensuGoConfig, ns notifications.WebhookSender, t
|
||||
APIKey: config.APIKey,
|
||||
Message: config.Message,
|
||||
log: log.New("alerting.notifier.sensugo"),
|
||||
images: images,
|
||||
ns: ns,
|
||||
tmpl: t,
|
||||
}
|
||||
@@ -134,7 +137,26 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
||||
handlers = []string{tmpl(sn.Handler)}
|
||||
}
|
||||
|
||||
labels := make(map[string]string)
|
||||
|
||||
var imageURL string
|
||||
_ = withStoredImages(ctx, sn.log, sn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
// If there is an image for this alert and the image has been uploaded
|
||||
// to a public URL then add it to the request. We cannot add more than
|
||||
// one image per request.
|
||||
if image != nil && image.URL != "" && imageURL == "" {
|
||||
imageURL = image.URL
|
||||
}
|
||||
return nil
|
||||
}, as...)
|
||||
if imageURL != "" {
|
||||
labels["imageURL"] = imageURL
|
||||
}
|
||||
|
||||
ruleURL := joinUrlPath(sn.tmpl.ExternalURL.String(), "/alerting/list", sn.log)
|
||||
labels["ruleURL"] = ruleURL
|
||||
|
||||
bodyMsgType := map[string]interface{}{
|
||||
"entity": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
@@ -144,10 +166,8 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
||||
},
|
||||
"check": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"name": check,
|
||||
"labels": map[string]string{
|
||||
"ruleURL": ruleURL,
|
||||
},
|
||||
"name": check,
|
||||
"labels": labels,
|
||||
},
|
||||
"output": tmpl(sn.Message),
|
||||
"issued": timeNow().Unix(),
|
||||
|
||||
@@ -27,6 +27,9 @@ func TestSensuGoNotifier(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
images, deleteFunc := newFakeImageStore(t)
|
||||
defer deleteFunc()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
@@ -42,7 +45,7 @@ func TestSensuGoNotifier(t *testing.T) {
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"__alert_rule_uid__": "rule uid", "alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__alertScreenshotToken__": "test-image"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -57,7 +60,8 @@ func TestSensuGoNotifier(t *testing.T) {
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "default",
|
||||
"labels": map[string]string{
|
||||
"ruleURL": "http://localhost/alerting/list",
|
||||
"imageURL": "https://www.example.com/test-image.jpg",
|
||||
"ruleURL": "http://localhost/alerting/list",
|
||||
},
|
||||
},
|
||||
"output": "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
|
||||
@@ -84,12 +88,12 @@ func TestSensuGoNotifier(t *testing.T) {
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"__alert_rule_uid__": "rule uid", "alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__alertScreenshotToken__": "test-image"},
|
||||
},
|
||||
}, {
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"},
|
||||
Annotations: model.LabelSet{"ann1": "annv2"},
|
||||
Annotations: model.LabelSet{"ann1": "annv2", "__alertScreenshotToken__": "test-image-2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -104,7 +108,8 @@ func TestSensuGoNotifier(t *testing.T) {
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "grafana_rule_0",
|
||||
"labels": map[string]string{
|
||||
"ruleURL": "http://localhost/alerting/list",
|
||||
"imageURL": "https://www.example.com/test-image.jpg",
|
||||
"ruleURL": "http://localhost/alerting/list",
|
||||
},
|
||||
},
|
||||
"output": "2 alerts are firing, 0 are resolved",
|
||||
@@ -157,7 +162,7 @@ func TestSensuGoNotifier(t *testing.T) {
|
||||
|
||||
ctx := notify.WithGroupKey(context.Background(), "alertname")
|
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""})
|
||||
sn := NewSensuGoNotifier(cfg, webhookSender, tmpl)
|
||||
sn := NewSensuGoNotifier(cfg, images, webhookSender, tmpl)
|
||||
ok, err := sn.Notify(ctx, c.alerts...)
|
||||
if c.expMsgError != nil {
|
||||
require.False(t, ok)
|
||||
|
||||
@@ -2,12 +2,61 @@ package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
// deleteFunc deletes the fake image.
|
||||
type deleteFunc func()
|
||||
|
||||
// newFakeImageStore creates a fake image on disk and returns an image
|
||||
// with a token, path and URL. The test should call deleteFunc to delete
|
||||
// the image from disk at the end of the test.
|
||||
func newFakeImageStore(t *testing.T) (ImageStore, deleteFunc) {
|
||||
f, err := os.CreateTemp("", "ngalert-test-image-*.png")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp image: %s", err)
|
||||
}
|
||||
|
||||
// 1x1 transparent PNG
|
||||
b, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode PNG data: %s", err)
|
||||
}
|
||||
|
||||
if _, err := f.Write(b); err != nil {
|
||||
t.Fatalf("failed to write to file: %s", err)
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("failed to close file: %s", err)
|
||||
}
|
||||
|
||||
fn := func() {
|
||||
if err := os.Remove(f.Name()); err != nil {
|
||||
t.Fatalf("failed to delete file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
store := &fakeImageStore{
|
||||
Images: []*ngmodels.Image{
|
||||
{
|
||||
Token: "test-image",
|
||||
Path: f.Name(),
|
||||
URL: "https://www.example.com/test-image.jpg",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return store, fn
|
||||
}
|
||||
|
||||
// mockTimeNow replaces function timeNow to return constant time.
|
||||
// It returns a function that resets the variable back to its original value.
|
||||
// This allows usage of this function with defer:
|
||||
|
||||
Reference in New Issue
Block a user