mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Bump grafana/alerting and refactor the ImageStore/Provider to provide image URL/bytes (#70182)
* implement alerting.images.Provider interface in our ImageStore * add URLExists() method to fakeConfigStore * make linter happy * update integration tests
This commit is contained in:
parent
3007e3b209
commit
ff9eff49bd
10
go.mod
10
go.mod
@ -58,7 +58,7 @@ require (
|
|||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/google/wire v0.5.0
|
github.com/google/wire v0.5.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/grafana/alerting v0.0.0-20230428095912-33c5aa68a5ba
|
github.com/grafana/alerting v0.0.0-20230606080147-55b8d71c7890
|
||||||
github.com/grafana/cuetsy v0.1.9
|
github.com/grafana/cuetsy v0.1.9
|
||||||
github.com/grafana/grafana-aws-sdk v0.15.0
|
github.com/grafana/grafana-aws-sdk v0.15.0
|
||||||
github.com/grafana/grafana-azure-sdk-go v1.7.0
|
github.com/grafana/grafana-azure-sdk-go v1.7.0
|
||||||
@ -91,7 +91,7 @@ require (
|
|||||||
github.com/prometheus/prometheus v1.8.2-0.20210621150501-ff58416a0b02
|
github.com/prometheus/prometheus v1.8.2-0.20210621150501-ff58416a0b02
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/goxmldsig v1.2.0
|
github.com/russellhaering/goxmldsig v1.2.0
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
||||||
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f
|
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f
|
||||||
github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
|
github.com/uber/jaeger-client-go v2.29.1+incompatible // indirect
|
||||||
@ -110,7 +110,7 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833
|
golang.org/x/exp v0.0.0-20230307190834-24139beb5833
|
||||||
golang.org/x/net v0.9.0
|
golang.org/x/net v0.9.0
|
||||||
golang.org/x/oauth2 v0.6.0
|
golang.org/x/oauth2 v0.6.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.3.0
|
||||||
golang.org/x/time v0.3.0
|
golang.org/x/time v0.3.0
|
||||||
golang.org/x/tools v0.7.0
|
golang.org/x/tools v0.7.0
|
||||||
gonum.org/v1/gonum v0.11.0
|
gonum.org/v1/gonum v0.11.0
|
||||||
@ -122,7 +122,7 @@ require (
|
|||||||
gopkg.in/mail.v2 v2.3.1
|
gopkg.in/mail.v2 v2.3.1
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
xorm.io/builder v0.3.6
|
xorm.io/builder v0.3.6 // indirect
|
||||||
xorm.io/core v0.7.3
|
xorm.io/core v0.7.3
|
||||||
xorm.io/xorm v0.8.2
|
xorm.io/xorm v0.8.2
|
||||||
)
|
)
|
||||||
@ -418,7 +418,7 @@ require (
|
|||||||
github.com/xlab/treeprint v1.1.0
|
github.com/xlab/treeprint v1.1.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||||
golang.org/x/mod v0.9.0 // indirect
|
golang.org/x/mod v0.9.0
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
10
go.sum
10
go.sum
@ -1356,8 +1356,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
|
|||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||||
github.com/grafana/alerting v0.0.0-20230428095912-33c5aa68a5ba h1:aNTu22ojw4XY24DYNAuvw8v/5iFUNk2bkdTeeWQ5+0o=
|
github.com/grafana/alerting v0.0.0-20230606080147-55b8d71c7890 h1:ubNIgVGX4PQ9YI1nWnt2mky3il8clWSjdo3NFSD26DQ=
|
||||||
github.com/grafana/alerting v0.0.0-20230428095912-33c5aa68a5ba/go.mod h1:5edgy6tQY4+W2wuJdi8g2GjbVmpJufguy7QGIRl2q4o=
|
github.com/grafana/alerting v0.0.0-20230606080147-55b8d71c7890/go.mod h1:zEflOvMVchYhRbFb5ziXVR/JG67FOLBzQTjhHh9xaI4=
|
||||||
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
|
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
|
||||||
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
|
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
|
||||||
github.com/grafana/cuetsy v0.1.9 h1:EwT8BqHoC0B3B4Y6Lg/D1aeYUKnQC1+2jjOHNsOuUfU=
|
github.com/grafana/cuetsy v0.1.9 h1:EwT8BqHoC0B3B4Y6Lg/D1aeYUKnQC1+2jjOHNsOuUfU=
|
||||||
@ -2380,8 +2380,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
|
|||||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||||
@ -2941,8 +2942,9 @@ golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -359,7 +359,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s := &sender{am.NotificationService}
|
s := &sender{am.NotificationService}
|
||||||
img := newImageStore(am.Store)
|
img := newImageProvider(am.Store, log.New("ngalert.notifier.image-provider"))
|
||||||
integrations, err := alertingNotify.BuildReceiverIntegrations(
|
integrations, err := alertingNotify.BuildReceiverIntegrations(
|
||||||
receiverCfg,
|
receiverCfg,
|
||||||
tmpl,
|
tmpl,
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/alerting/images"
|
alertingImages "github.com/grafana/alerting/images"
|
||||||
alertingLogging "github.com/grafana/alerting/logging"
|
alertingLogging "github.com/grafana/alerting/logging"
|
||||||
"github.com/grafana/alerting/receivers"
|
"github.com/grafana/alerting/receivers"
|
||||||
alertingEmail "github.com/grafana/alerting/receivers/email"
|
alertingEmail "github.com/grafana/alerting/receivers/email"
|
||||||
@ -200,7 +200,7 @@ func createSut(t *testing.T, messageTmpl string, subjectTmpl string, emailTmpl *
|
|||||||
},
|
},
|
||||||
Message: messageTmpl,
|
Message: messageTmpl,
|
||||||
Subject: subjectTmpl,
|
Subject: subjectTmpl,
|
||||||
}, receivers.Metadata{}, emailTmpl, ns, &images.UnavailableImageStore{}, &alertingLogging.FakeLogger{})
|
}, receivers.Metadata{}, emailTmpl, ns, &alertingImages.UnavailableProvider{}, &alertingLogging.FakeLogger{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSingleSentMessage(t *testing.T, ns *emailSender) *notifications.Message {
|
func getSingleSentMessage(t *testing.T, ns *emailSender) *notifications.Message {
|
||||||
|
@ -2,45 +2,154 @@ package notifier
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/alerting/images"
|
alertingImages "github.com/grafana/alerting/images"
|
||||||
|
alertingModels "github.com/grafana/alerting/models"
|
||||||
|
alertingNotify "github.com/grafana/alerting/notify"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type imageStore struct {
|
type imageProvider struct {
|
||||||
store store.ImageStore
|
store store.ImageStore
|
||||||
|
logger log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newImageStore(store store.ImageStore) images.ImageStore {
|
func newImageProvider(store store.ImageStore, logger log.Logger) alertingImages.Provider {
|
||||||
return &imageStore{
|
return &imageProvider{
|
||||||
store: store,
|
store: store,
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i imageStore) GetImage(ctx context.Context, uri string) (*images.Image, error) {
|
func (i imageProvider) GetImage(ctx context.Context, uri string) (*alertingImages.Image, error) {
|
||||||
var (
|
image, err := i.getImageFromURI(ctx, uri)
|
||||||
image *models.Image
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check whether the uri is a URL or a token to know how to query the DB.
|
|
||||||
if strings.HasPrefix(uri, "http") {
|
|
||||||
image, err = i.store.GetImageByURL(ctx, uri)
|
|
||||||
} else {
|
|
||||||
token := strings.TrimPrefix(uri, "token://")
|
|
||||||
image, err = i.store.GetImage(ctx, token)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, models.ErrImageNotFound) {
|
||||||
|
i.logger.Info("Image not found in database")
|
||||||
|
return nil, alertingImages.ErrImageNotFound
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &images.Image{
|
return &alertingImages.Image{
|
||||||
Token: image.Token,
|
Token: image.Token,
|
||||||
Path: image.Path,
|
Path: image.Path,
|
||||||
URL: image.URL,
|
URL: image.URL,
|
||||||
CreatedAt: image.CreatedAt,
|
CreatedAt: image.CreatedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i imageProvider) GetImageURL(ctx context.Context, alert *alertingNotify.Alert) (string, error) {
|
||||||
|
uri, err := getImageURI(alert)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the identifier is a URL, validate that it corresponds to a stored, non-expired image.
|
||||||
|
if strings.HasPrefix(uri, "http") {
|
||||||
|
i.logger.Debug("Received an image URL in annotations", "alert", alert)
|
||||||
|
exists, err := i.store.URLExists(ctx, uri)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
i.logger.Info("Image URL not found in database", "alert", alert)
|
||||||
|
return "", alertingImages.ErrImageNotFound
|
||||||
|
}
|
||||||
|
return uri, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the identifier is a token, remove the prefix, get the image and return the URL.
|
||||||
|
token := strings.TrimPrefix(uri, "token://")
|
||||||
|
i.logger.Debug("Received an image token in annotations", "alert", alert, "token", token)
|
||||||
|
return i.getImageURLFromToken(ctx, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getImageURLFromToken takes a token and returns the URL of the image that token belongs to.
|
||||||
|
func (i imageProvider) getImageURLFromToken(ctx context.Context, token string) (string, error) {
|
||||||
|
image, err := i.store.GetImage(ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, models.ErrImageNotFound) {
|
||||||
|
i.logger.Info("Image not found in database", "token", token)
|
||||||
|
return "", alertingImages.ErrImageNotFound
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !image.HasURL() {
|
||||||
|
return "", alertingImages.ErrImagesNoURL
|
||||||
|
}
|
||||||
|
return image.URL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i imageProvider) GetRawImage(ctx context.Context, alert *alertingNotify.Alert) (io.ReadCloser, string, error) {
|
||||||
|
uri, err := getImageURI(alert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
image, err := i.getImageFromURI(ctx, uri)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, models.ErrImageNotFound) {
|
||||||
|
i.logger.Info("Image not found in database", "alert", alert)
|
||||||
|
return nil, "", alertingImages.ErrImageNotFound
|
||||||
|
}
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if !image.HasPath() {
|
||||||
|
return nil, "", alertingImages.ErrImagesNoPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return image bytes and filename.
|
||||||
|
readCloser, err := openImage(image.Path)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Error("Error looking for image on disk", "alert", alert, "path", image.Path, "error", err)
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
filename := filepath.Base(image.Path)
|
||||||
|
return readCloser, filename, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i imageProvider) getImageFromURI(ctx context.Context, uri string) (*models.Image, error) {
|
||||||
|
// Check whether the uri is a URL or a token to know how to query the DB.
|
||||||
|
if strings.HasPrefix(uri, "http") {
|
||||||
|
i.logger.Debug("Received an image URL in annotations")
|
||||||
|
return i.store.GetImageByURL(ctx, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := strings.TrimPrefix(uri, "token://")
|
||||||
|
i.logger.Debug("Received an image token in annotations", "token", token)
|
||||||
|
return i.store.GetImage(ctx, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getImageURI is a helper function to retrieve the image URI from the alert annotations as a string.
|
||||||
|
func getImageURI(alert *alertingNotify.Alert) (string, error) {
|
||||||
|
uri, ok := alert.Annotations[alertingModels.ImageTokenAnnotation]
|
||||||
|
if !ok {
|
||||||
|
return "", alertingImages.ErrNoImageForAlert
|
||||||
|
}
|
||||||
|
return string(uri), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openImage returns an the io representation of an image from the given path.
|
||||||
|
func openImage(path string) (io.ReadCloser, error) {
|
||||||
|
fp := filepath.Clean(path)
|
||||||
|
_, err := os.Stat(fp)
|
||||||
|
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||||
|
return nil, alertingImages.ErrImageNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
@ -2,16 +2,25 @@ package notifier
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
alertingImages "github.com/grafana/alerting/images"
|
||||||
|
alertingModels "github.com/grafana/alerting/models"
|
||||||
|
alertingNotify "github.com/grafana/alerting/notify"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetImage(t *testing.T) {
|
func TestGetImage(t *testing.T) {
|
||||||
fakeImageStore := store.NewFakeImageStore(t)
|
fakeImageStore := store.NewFakeImageStore(t)
|
||||||
store := newImageStore(fakeImageStore)
|
store := newImageProvider(fakeImageStore, log.NewNopLogger())
|
||||||
|
|
||||||
t.Run("queries by token when it gets a token", func(tt *testing.T) {
|
t.Run("queries by token when it gets a token", func(tt *testing.T) {
|
||||||
img := models.Image{
|
img := models.Image{
|
||||||
@ -22,6 +31,7 @@ func TestGetImage(t *testing.T) {
|
|||||||
err := fakeImageStore.SaveImage(context.Background(), &img)
|
err := fakeImageStore.SaveImage(context.Background(), &img)
|
||||||
require.NoError(tt, err)
|
require.NoError(tt, err)
|
||||||
|
|
||||||
|
// nolint:staticcheck
|
||||||
savedImg, err := store.GetImage(context.Background(), "token://"+img.Token)
|
savedImg, err := store.GetImage(context.Background(), "token://"+img.Token)
|
||||||
require.NoError(tt, err)
|
require.NoError(tt, err)
|
||||||
require.Equal(tt, savedImg.Token, img.Token)
|
require.Equal(tt, savedImg.Token, img.Token)
|
||||||
@ -38,6 +48,7 @@ func TestGetImage(t *testing.T) {
|
|||||||
err := fakeImageStore.SaveImage(context.Background(), &img)
|
err := fakeImageStore.SaveImage(context.Background(), &img)
|
||||||
require.NoError(tt, err)
|
require.NoError(tt, err)
|
||||||
|
|
||||||
|
// nolint:staticcheck
|
||||||
savedImg, err := store.GetImage(context.Background(), img.URL)
|
savedImg, err := store.GetImage(context.Background(), img.URL)
|
||||||
require.NoError(tt, err)
|
require.NoError(tt, err)
|
||||||
require.Equal(tt, savedImg.Token, img.Token)
|
require.Equal(tt, savedImg.Token, img.Token)
|
||||||
@ -45,3 +56,170 @@ func TestGetImage(t *testing.T) {
|
|||||||
require.Equal(tt, savedImg.Path, img.Path)
|
require.Equal(tt, savedImg.Path, img.Path)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetImageURL(t *testing.T) {
|
||||||
|
var (
|
||||||
|
imageWithoutURL = models.Image{
|
||||||
|
Token: "test-no-url",
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
ExpiresAt: time.Now().UTC().Add(24 * time.Hour),
|
||||||
|
}
|
||||||
|
testImage = models.Image{
|
||||||
|
Token: "test",
|
||||||
|
URL: "https://test.com",
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
ExpiresAt: time.Now().UTC().Add(24 * time.Hour),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fakeImageStore := store.NewFakeImageStore(t, &imageWithoutURL, &testImage)
|
||||||
|
store := newImageProvider(fakeImageStore, log.NewNopLogger())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uri string
|
||||||
|
expURL string
|
||||||
|
expErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"URL does not exist",
|
||||||
|
"https://invalid.com/test",
|
||||||
|
"",
|
||||||
|
alertingImages.ErrImageNotFound,
|
||||||
|
}, {
|
||||||
|
"existing URL",
|
||||||
|
testImage.URL,
|
||||||
|
testImage.URL,
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
"token does not exist",
|
||||||
|
"token://invalid",
|
||||||
|
"",
|
||||||
|
alertingImages.ErrImageNotFound,
|
||||||
|
}, {
|
||||||
|
"existing token",
|
||||||
|
"token://" + testImage.Token,
|
||||||
|
testImage.URL,
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
"image has no URL",
|
||||||
|
"token://" + imageWithoutURL.Token,
|
||||||
|
"",
|
||||||
|
alertingImages.ErrImagesNoURL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(tt *testing.T) {
|
||||||
|
alert := alertingNotify.Alert{
|
||||||
|
Alert: model.Alert{
|
||||||
|
Annotations: model.LabelSet{alertingModels.ImageTokenAnnotation: model.LabelValue(test.uri)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url, err := store.GetImageURL(context.Background(), &alert)
|
||||||
|
require.ErrorIs(tt, err, test.expErr)
|
||||||
|
require.Equal(tt, test.expURL, url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRawImage(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testBytes = []byte("some test bytes")
|
||||||
|
testPath = generateTestFile(t, testBytes)
|
||||||
|
imageWithoutPath = models.Image{
|
||||||
|
Token: "test-no-path",
|
||||||
|
URL: "https://test-no-path.com",
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
ExpiresAt: time.Now().UTC().Add(24 * time.Hour),
|
||||||
|
}
|
||||||
|
testImage = models.Image{
|
||||||
|
Token: "test",
|
||||||
|
URL: "https://test.com",
|
||||||
|
Path: testPath,
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
ExpiresAt: time.Now().UTC().Add(24 * time.Hour),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fakeImageStore := store.NewFakeImageStore(t, &imageWithoutPath, &testImage)
|
||||||
|
store := newImageProvider(fakeImageStore, log.NewNopLogger())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uri string
|
||||||
|
expFilename string
|
||||||
|
expBytes []byte
|
||||||
|
expErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"URL does not exist",
|
||||||
|
"https://invalid.com/test",
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
alertingImages.ErrImageNotFound,
|
||||||
|
}, {
|
||||||
|
"existing URL",
|
||||||
|
testImage.URL,
|
||||||
|
filepath.Base(testPath),
|
||||||
|
testBytes,
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
"token does not exist",
|
||||||
|
"token://invalid",
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
alertingImages.ErrImageNotFound,
|
||||||
|
}, {
|
||||||
|
"existing token",
|
||||||
|
"token://" + testImage.Token,
|
||||||
|
filepath.Base(testPath),
|
||||||
|
testBytes,
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
"image has no path",
|
||||||
|
"token://" + imageWithoutPath.Token,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
alertingImages.ErrImagesNoPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(tt *testing.T) {
|
||||||
|
alert := alertingNotify.Alert{
|
||||||
|
Alert: model.Alert{
|
||||||
|
Annotations: model.LabelSet{alertingModels.ImageTokenAnnotation: model.LabelValue(test.uri)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
readCloser, filename, err := store.GetRawImage(context.Background(), &alert)
|
||||||
|
require.ErrorIs(tt, err, test.expErr)
|
||||||
|
require.Equal(tt, test.expFilename, filename)
|
||||||
|
|
||||||
|
if test.expBytes != nil {
|
||||||
|
b, err := io.ReadAll(readCloser)
|
||||||
|
require.NoError(tt, err)
|
||||||
|
require.Equal(tt, test.expBytes, b)
|
||||||
|
require.NoError(t, readCloser.Close())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTestFile(t *testing.T, b []byte) string {
|
||||||
|
t.Helper()
|
||||||
|
f, err := os.CreateTemp("/tmp", "image")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func(f *os.File) {
|
||||||
|
_ = f.Close()
|
||||||
|
}(f)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, os.RemoveAll(f.Name()))
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = f.Write(b)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
@ -13,6 +13,8 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
|
|
||||||
|
alertingImages "github.com/grafana/alerting/images"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeConfigStore struct {
|
type fakeConfigStore struct {
|
||||||
@ -24,19 +26,23 @@ type fakeConfigStore struct {
|
|||||||
|
|
||||||
// Saves the image or returns an error.
|
// Saves the image or returns an error.
|
||||||
func (f *fakeConfigStore) SaveImage(ctx context.Context, img *models.Image) error {
|
func (f *fakeConfigStore) SaveImage(ctx context.Context, img *models.Image) error {
|
||||||
return models.ErrImageNotFound
|
return alertingImages.ErrImageNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeConfigStore) GetImage(ctx context.Context, token string) (*models.Image, error) {
|
func (f *fakeConfigStore) GetImage(ctx context.Context, token string) (*models.Image, error) {
|
||||||
return nil, models.ErrImageNotFound
|
return nil, alertingImages.ErrImageNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeConfigStore) GetImageByURL(ctx context.Context, url string) (*models.Image, error) {
|
func (f *fakeConfigStore) GetImageByURL(ctx context.Context, url string) (*models.Image, error) {
|
||||||
return nil, models.ErrImageNotFound
|
return nil, alertingImages.ErrImageNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeConfigStore) URLExists(ctx context.Context, url string) (bool, error) {
|
||||||
|
return false, alertingImages.ErrImageNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeConfigStore) GetImages(ctx context.Context, tokens []string) ([]models.Image, []string, error) {
|
func (f *fakeConfigStore) GetImages(ctx context.Context, tokens []string) ([]models.Image, []string, error) {
|
||||||
return nil, nil, models.ErrImageNotFound
|
return nil, nil, alertingImages.ErrImageNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFakeConfigStore(t *testing.T, configs map[int64]*models.AlertConfiguration) *fakeConfigStore {
|
func NewFakeConfigStore(t *testing.T, configs map[int64]*models.AlertConfiguration) *fakeConfigStore {
|
||||||
|
@ -31,6 +31,10 @@ type ImageStore interface {
|
|||||||
|
|
||||||
// SaveImage saves the image or returns an error.
|
// SaveImage saves the image or returns an error.
|
||||||
SaveImage(ctx context.Context, img *models.Image) error
|
SaveImage(ctx context.Context, img *models.Image) error
|
||||||
|
|
||||||
|
// URLExists takes a URL and returns a boolean indicating whether or not
|
||||||
|
// we have an image for that URL.
|
||||||
|
URLExists(ctx context.Context, url string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageAdminStore interface {
|
type ImageAdminStore interface {
|
||||||
@ -75,6 +79,19 @@ func (st DBstore) GetImageByURL(ctx context.Context, url string) (*models.Image,
|
|||||||
return &image, nil
|
return &image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st DBstore) URLExists(ctx context.Context, url string) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||||
|
ok, err := sess.Table("alert_image").Where("url = ? AND expires_at > ?", url, TimeNow().UTC()).Exist()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exists = ok
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
func (st DBstore) GetImages(ctx context.Context, tokens []string) ([]models.Image, []string, error) {
|
func (st DBstore) GetImages(ctx context.Context, tokens []string) ([]models.Image, []string, error) {
|
||||||
var images []models.Image
|
var images []models.Image
|
||||||
if err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
if err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||||
|
@ -9,10 +9,15 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewFakeImageStore(t *testing.T) *FakeImageStore {
|
func NewFakeImageStore(t *testing.T, images ...*models.Image) *FakeImageStore {
|
||||||
|
imageMap := make(map[string]*models.Image)
|
||||||
|
for _, image := range images {
|
||||||
|
imageMap[image.Token] = image
|
||||||
|
}
|
||||||
|
|
||||||
return &FakeImageStore{
|
return &FakeImageStore{
|
||||||
t: t,
|
t: t,
|
||||||
images: make(map[string]*models.Image),
|
images: imageMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +48,17 @@ func (s *FakeImageStore) GetImageByURL(_ context.Context, url string) (*models.I
|
|||||||
return nil, models.ErrImageNotFound
|
return nil, models.ErrImageNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FakeImageStore) URLExists(_ context.Context, url string) (bool, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
for _, image := range s.images {
|
||||||
|
if image.URL == url {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FakeImageStore) GetImages(_ context.Context, tokens []string) ([]models.Image, []string, error) {
|
func (s *FakeImageStore) GetImages(_ context.Context, tokens []string) ([]models.Image, []string, error) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
defer s.mtx.Unlock()
|
||||||
|
@ -24,6 +24,7 @@ func AddTablesMigrations(mg *migrator.Migrator) {
|
|||||||
mg.AddMigration("add last_applied column to alert_configuration_history", migrator.NewAddColumnMigration(migrator.Table{Name: "alert_configuration_history"}, &migrator.Column{
|
mg.AddMigration("add last_applied column to alert_configuration_history", migrator.NewAddColumnMigration(migrator.Table{Name: "alert_configuration_history"}, &migrator.Column{
|
||||||
Name: "last_applied", Type: migrator.DB_Int, Nullable: false, Default: "0",
|
Name: "last_applied", Type: migrator.DB_Int, Nullable: false, Default: "0",
|
||||||
}))
|
}))
|
||||||
|
// End of migration log, add new migrations above this line.
|
||||||
}
|
}
|
||||||
|
|
||||||
// historicalTableMigrations contains those migrations that existed prior to creating the improved messaging around migration immutability.
|
// historicalTableMigrations contains those migrations that existed prior to creating the improved messaging around migration immutability.
|
||||||
|
@ -2312,9 +2312,9 @@ var expEmailNotifications = []*notifications.SendEmailCommandSync{
|
|||||||
Annotations: template.KV{},
|
Annotations: template.KV{},
|
||||||
StartsAt: time.Time{},
|
StartsAt: time.Time{},
|
||||||
EndsAt: time.Time{},
|
EndsAt: time.Time{},
|
||||||
GeneratorURL: "http://localhost:3000/alerting/grafana/UID_EmailAlert/view",
|
GeneratorURL: "http://localhost:3000/alerting/grafana/UID_EmailAlert/view?orgId=1",
|
||||||
Fingerprint: "1e8f5e886dc14813",
|
Fingerprint: "1e8f5e886dc14813",
|
||||||
SilenceURL: "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DEmailAlert&matcher=grafana_folder%3Ddefault",
|
SilenceURL: "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DEmailAlert&matcher=grafana_folder%3Ddefault&orgId=1",
|
||||||
DashboardURL: "",
|
DashboardURL: "",
|
||||||
PanelURL: "",
|
PanelURL: "",
|
||||||
Values: map[string]float64{"A": 1},
|
Values: map[string]float64{"A": 1},
|
||||||
@ -2366,7 +2366,7 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
{
|
{
|
||||||
"title": "[FIRING:1] SlackAlert2 (default)",
|
"title": "[FIRING:1] SlackAlert2 (default)",
|
||||||
"title_link": "http://localhost:3000/alerting/list",
|
"title_link": "http://localhost:3000/alerting/list",
|
||||||
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SlackAlert2\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SlackAlert2/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSlackAlert2&matcher=grafana_folder%%3Ddefault\n",
|
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SlackAlert2\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SlackAlert2/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSlackAlert2&matcher=grafana_folder%%3Ddefault&orgId=1\n",
|
||||||
"fallback": "[FIRING:1] SlackAlert2 (default)",
|
"fallback": "[FIRING:1] SlackAlert2 (default)",
|
||||||
"footer": "Grafana v",
|
"footer": "Grafana v",
|
||||||
"footer_icon": "https://grafana.com/static/assets/img/fav32.png",
|
"footer_icon": "https://grafana.com/static/assets/img/fav32.png",
|
||||||
@ -2391,7 +2391,7 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
"component": "Integration Test",
|
"component": "Integration Test",
|
||||||
"group": "testgroup",
|
"group": "testgroup",
|
||||||
"custom_details": {
|
"custom_details": {
|
||||||
"firing": "\nValue: A=1\nLabels:\n - alertname = PagerdutyAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PagerdutyAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DPagerdutyAlert&matcher=grafana_folder%%3Ddefault\n",
|
"firing": "\nValue: A=1\nLabels:\n - alertname = PagerdutyAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PagerdutyAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DPagerdutyAlert&matcher=grafana_folder%%3Ddefault&orgId=1\n",
|
||||||
"num_firing": "1",
|
"num_firing": "1",
|
||||||
"num_resolved": "0",
|
"num_resolved": "0",
|
||||||
"resolved": ""
|
"resolved": ""
|
||||||
@ -2411,7 +2411,7 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
`{
|
`{
|
||||||
"link": {
|
"link": {
|
||||||
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%3A3000%2Falerting%2Flist",
|
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%3A3000%2Falerting%2Flist",
|
||||||
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DingDingAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DingDingAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDingDingAlert&matcher=grafana_folder%3Ddefault\n",
|
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DingDingAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DingDingAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDingDingAlert&matcher=grafana_folder%3Ddefault&orgId=1\n",
|
||||||
"title": "[FIRING:1] DingDingAlert (default)"
|
"title": "[FIRING:1] DingDingAlert (default)"
|
||||||
},
|
},
|
||||||
"msgtype": "link"
|
"msgtype": "link"
|
||||||
@ -2432,7 +2432,7 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
"weight": "bolder",
|
"weight": "bolder",
|
||||||
"wrap": true
|
"wrap": true
|
||||||
}, {
|
}, {
|
||||||
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = TeamsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TeamsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DTeamsAlert\u0026matcher=grafana_folder%3Ddefault\n",
|
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = TeamsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TeamsAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana\u0026matcher=alertname%3DTeamsAlert\u0026matcher=grafana_folder%3Ddefault&orgId=1\n",
|
||||||
"type": "TextBlock",
|
"type": "TextBlock",
|
||||||
"wrap": true
|
"wrap": true
|
||||||
}, {
|
}, {
|
||||||
@ -2476,9 +2476,9 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
"values": {"A": 1},
|
"values": {"A": 1},
|
||||||
"valueString": "[ var='A' labels={} value=1 ]",
|
"valueString": "[ var='A' labels={} value=1 ]",
|
||||||
"endsAt": "0001-01-01T00:00:00Z",
|
"endsAt": "0001-01-01T00:00:00Z",
|
||||||
"generatorURL": "http://localhost:3000/alerting/grafana/UID_WebhookAlert/view",
|
"generatorURL": "http://localhost:3000/alerting/grafana/UID_WebhookAlert/view?orgId=1",
|
||||||
"fingerprint": "15c59b0a380bd9f1",
|
"fingerprint": "15c59b0a380bd9f1",
|
||||||
"silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert&matcher=grafana_folder%%3Ddefault",
|
"silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert&matcher=grafana_folder%%3Ddefault&orgId=1",
|
||||||
"dashboardURL": "",
|
"dashboardURL": "",
|
||||||
"panelURL": ""
|
"panelURL": ""
|
||||||
}
|
}
|
||||||
@ -2497,12 +2497,12 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
"truncatedAlerts": 0,
|
"truncatedAlerts": 0,
|
||||||
"title": "[FIRING:1] WebhookAlert (default)",
|
"title": "[FIRING:1] WebhookAlert (default)",
|
||||||
"state": "alerting",
|
"state": "alerting",
|
||||||
"message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = WebhookAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_WebhookAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert&matcher=grafana_folder%%3Ddefault\n"
|
"message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = WebhookAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_WebhookAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DWebhookAlert&matcher=grafana_folder%%3Ddefault&orgId=1\n"
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
"discord_recv/discord_test": {
|
"discord_recv/discord_test": {
|
||||||
`{
|
`{
|
||||||
"content": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DiscordAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DiscordAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDiscordAlert&matcher=grafana_folder%3Ddefault\n",
|
"content": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = DiscordAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_DiscordAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DDiscordAlert&matcher=grafana_folder%3Ddefault&orgId=1\n",
|
||||||
"embeds": [
|
"embeds": [
|
||||||
{
|
{
|
||||||
"color": 14037554,
|
"color": 14037554,
|
||||||
@ -2530,7 +2530,7 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
},
|
},
|
||||||
"name": "default"
|
"name": "default"
|
||||||
},
|
},
|
||||||
"output": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SensuGoAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SensuGoAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSensuGoAlert&matcher=grafana_folder%%3Ddefault\n",
|
"output": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = SensuGoAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_SensuGoAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DSensuGoAlert&matcher=grafana_folder%%3Ddefault&orgId=1\n",
|
||||||
"status": 2
|
"status": 2
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@ -2543,10 +2543,10 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
"pushover_recv/pushover_test": {
|
"pushover_recv/pushover_test": {
|
||||||
"--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert (default)\r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = PushoverAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PushoverAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DPushoverAlert&matcher=grafana_folder%3Ddefault\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n",
|
"--abcd\r\nContent-Disposition: form-data; name=\"user\"\r\n\r\nmysecretkey\r\n--abcd\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\nmysecrettoken\r\n--abcd\r\nContent-Disposition: form-data; name=\"priority\"\r\n\r\n0\r\n--abcd\r\nContent-Disposition: form-data; name=\"sound\"\r\n\r\n\r\n--abcd\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n[FIRING:1] PushoverAlert (default)\r\n--abcd\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\nhttp://localhost:3000/alerting/list\r\n--abcd\r\nContent-Disposition: form-data; name=\"url_title\"\r\n\r\nShow alert rule\r\n--abcd\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = PushoverAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_PushoverAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DPushoverAlert&matcher=grafana_folder%3Ddefault&orgId=1\r\n--abcd\r\nContent-Disposition: form-data; name=\"html\"\r\n\r\n1\r\n--abcd--\r\n",
|
||||||
},
|
},
|
||||||
"telegram_recv/bot6sh027hs034h": {
|
"telegram_recv/bot6sh027hs034h": {
|
||||||
"--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nHTML\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = TelegramAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TelegramAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTelegramAlert&matcher=grafana_folder%3Ddefault\n\r\n--abcd--\r\n",
|
"--abcd\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\ntelegram_chat_id\r\n--abcd\r\nContent-Disposition: form-data; name=\"parse_mode\"\r\n\r\nHTML\r\n--abcd\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = TelegramAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_TelegramAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTelegramAlert&matcher=grafana_folder%3Ddefault&orgId=1\n\r\n--abcd--\r\n",
|
||||||
},
|
},
|
||||||
"googlechat_recv/googlechat_test": {
|
"googlechat_recv/googlechat_test": {
|
||||||
`{
|
`{
|
||||||
@ -2562,7 +2562,7 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
"widgets": [
|
"widgets": [
|
||||||
{
|
{
|
||||||
"textParagraph": {
|
"textParagraph": {
|
||||||
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = GoogleChatAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_GoogleChatAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DGoogleChatAlert&matcher=grafana_folder%%3Ddefault\n"
|
"text": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = GoogleChatAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_GoogleChatAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DGoogleChatAlert&matcher=grafana_folder%%3Ddefault&orgId=1\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2600,7 +2600,7 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
"client": "Grafana",
|
"client": "Grafana",
|
||||||
"client_url": "http://localhost:3000/alerting/list",
|
"client_url": "http://localhost:3000/alerting/list",
|
||||||
"description": "[FIRING:1] KafkaAlert (default)",
|
"description": "[FIRING:1] KafkaAlert (default)",
|
||||||
"details": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = KafkaAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_KafkaAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DKafkaAlert&matcher=grafana_folder%3Ddefault\n",
|
"details": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = KafkaAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_KafkaAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DKafkaAlert&matcher=grafana_folder%3Ddefault&orgId=1\n",
|
||||||
"incident_key": "35c0bdb1715f9162a20d7b2a01cb2e3a4c5b1dc663571701e3f67212b696332f"
|
"incident_key": "35c0bdb1715f9162a20d7b2a01cb2e3a4c5b1dc663571701e3f67212b696332f"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2608,10 +2608,10 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
"line_recv/line_test": {
|
"line_recv/line_test": {
|
||||||
`message=%5BFIRING%3A1%5D+LineAlert+%28default%29%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_LineAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DLineAlert%26matcher%3Dgrafana_folder%253Ddefault%0A`,
|
`message=%5BFIRING%3A1%5D+LineAlert+%28default%29%0Ahttp%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+LineAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_LineAlert%2Fview%3ForgId%3D1%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DLineAlert%26matcher%3Dgrafana_folder%253Ddefault%26orgId%3D1%0A`,
|
||||||
},
|
},
|
||||||
"threema_recv/threema_test": {
|
"threema_recv/threema_test": {
|
||||||
`from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%28default%29%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_ThreemaAlert%2Fview%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DThreemaAlert%26matcher%3Dgrafana_folder%253Ddefault%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`,
|
`from=%2A1234567&secret=myapisecret&text=%E2%9A%A0%EF%B8%8F+%5BFIRING%3A1%5D+ThreemaAlert+%28default%29%0A%0A%2AMessage%3A%2A%0A%2A%2AFiring%2A%2A%0A%0AValue%3A+A%3D1%0ALabels%3A%0A+-+alertname+%3D+ThreemaAlert%0A+-+grafana_folder+%3D+default%0AAnnotations%3A%0ASource%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fgrafana%2FUID_ThreemaAlert%2Fview%3ForgId%3D1%0ASilence%3A+http%3A%2F%2Flocalhost%3A3000%2Falerting%2Fsilence%2Fnew%3Falertmanager%3Dgrafana%26matcher%3Dalertname%253DThreemaAlert%26matcher%3Dgrafana_folder%253Ddefault%26orgId%3D1%0A%0A%2AURL%3A%2A+http%3A%2Flocalhost%3A3000%2Falerting%2Flist%0A&to=abcdefgh`,
|
||||||
},
|
},
|
||||||
"victorops_recv/victorops_test": {
|
"victorops_recv/victorops_test": {
|
||||||
`{
|
`{
|
||||||
@ -2620,14 +2620,14 @@ var expNonEmailNotifications = map[string][]string{
|
|||||||
"entity_id": "633ae988fa7074bcb51f3d1c5fef2ba1c5c4ccb45b3ecbf681f7d507b078b1ae",
|
"entity_id": "633ae988fa7074bcb51f3d1c5fef2ba1c5c4ccb45b3ecbf681f7d507b078b1ae",
|
||||||
"message_type": "CRITICAL",
|
"message_type": "CRITICAL",
|
||||||
"monitoring_tool": "Grafana v",
|
"monitoring_tool": "Grafana v",
|
||||||
"state_message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = VictorOpsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_VictorOpsAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DVictorOpsAlert&matcher=grafana_folder%%3Ddefault\n",
|
"state_message": "**Firing**\n\nValue: A=1\nLabels:\n - alertname = VictorOpsAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_VictorOpsAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%%3DVictorOpsAlert&matcher=grafana_folder%%3Ddefault&orgId=1\n",
|
||||||
"timestamp": %s
|
"timestamp": %s
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
"opsgenie_recv/opsgenie_test": {
|
"opsgenie_recv/opsgenie_test": {
|
||||||
`{
|
`{
|
||||||
"alias": "47e92f0f6ef9fe99f3954e0d6155f8d09c4b9a038d8c3105e82c0cee4c62956e",
|
"alias": "47e92f0f6ef9fe99f3954e0d6155f8d09c4b9a038d8c3105e82c0cee4c62956e",
|
||||||
"description": "[FIRING:1] OpsGenieAlert (default)\nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = OpsGenieAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_OpsGenieAlert/view\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DOpsGenieAlert&matcher=grafana_folder%3Ddefault\n",
|
"description": "[FIRING:1] OpsGenieAlert (default)\nhttp://localhost:3000/alerting/list\n\n**Firing**\n\nValue: A=1\nLabels:\n - alertname = OpsGenieAlert\n - grafana_folder = default\nAnnotations:\nSource: http://localhost:3000/alerting/grafana/UID_OpsGenieAlert/view?orgId=1\nSilence: http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DOpsGenieAlert&matcher=grafana_folder%3Ddefault&orgId=1\n",
|
||||||
"details": {
|
"details": {
|
||||||
"url": "http://localhost:3000/alerting/list"
|
"url": "http://localhost:3000/alerting/list"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user