mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: isolate ImageStore in notify package (#60353)
This commit is contained in:
parent
286af5a53b
commit
de008005ce
@ -514,7 +514,7 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec
|
|||||||
SecureSettings: secureSettings,
|
SecureSettings: secureSettings,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
factoryConfig, err := channels.NewFactoryConfig(cfg, NewNotificationSender(am.NotificationService), am.decryptFn, tmpl, am.Store)
|
factoryConfig, err := channels.NewFactoryConfig(cfg, NewNotificationSender(am.NotificationService), am.decryptFn, tmpl, newImageStore(am.Store))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, InvalidReceiverError{
|
return nil, InvalidReceiverError{
|
||||||
Receiver: r,
|
Receiver: r,
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetDecryptedValueFn is a function that returns the decrypted value of
|
// GetDecryptedValueFn is a function that returns the decrypted value of
|
||||||
@ -104,7 +103,7 @@ func (n *AlertmanagerNotifier) Notify(ctx context.Context, as ...*types.Alert) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = withStoredImages(ctx, n.logger, n.images,
|
_ = withStoredImages(ctx, n.logger, n.images,
|
||||||
func(index int, image ngmodels.Image) error {
|
func(index int, image Image) error {
|
||||||
// If there is an image for this alert and the image has been uploaded
|
// If there is an image for this alert and the image has been uploaded
|
||||||
// to a public URL then include it as an annotation
|
// to a public URL then include it as an annotation
|
||||||
if image.URL != "" {
|
if image.URL != "" {
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -193,7 +192,7 @@ func (d DiscordNotifier) constructAttachments(ctx context.Context, as []*types.A
|
|||||||
attachments := make([]discordAttachment, 0)
|
attachments := make([]discordAttachment, 0)
|
||||||
|
|
||||||
_ = withStoredImages(ctx, d.log, d.images,
|
_ = withStoredImages(ctx, d.log, d.images,
|
||||||
func(index int, image ngmodels.Image) error {
|
func(index int, image Image) error {
|
||||||
if embedQuota < 1 {
|
if embedQuota < 1 {
|
||||||
return ErrImagesDone
|
return ErrImagesDone
|
||||||
}
|
}
|
||||||
@ -213,7 +212,7 @@ func (d DiscordNotifier) constructAttachments(ctx context.Context, as []*types.A
|
|||||||
base := filepath.Base(image.Path)
|
base := filepath.Base(image.Path)
|
||||||
url := fmt.Sprintf("attachment://%s", base)
|
url := fmt.Sprintf("attachment://%s", base)
|
||||||
reader, err := openImage(image.Path)
|
reader, err := openImage(image.Path)
|
||||||
if err != nil && !errors.Is(err, ngmodels.ErrImageNotFound) {
|
if err != nil && !errors.Is(err, ErrImageNotFound) {
|
||||||
d.log.Warn("failed to retrieve image data from store", "error", err)
|
d.log.Warn("failed to retrieve image data from store", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -110,7 +109,7 @@ func (en *EmailNotifier) Notify(ctx context.Context, alerts ...*types.Alert) (bo
|
|||||||
// Extend alerts data with images, if available.
|
// Extend alerts data with images, if available.
|
||||||
var embeddedFiles []string
|
var embeddedFiles []string
|
||||||
_ = withStoredImages(ctx, en.log, en.images,
|
_ = withStoredImages(ctx, en.log, en.images,
|
||||||
func(index int, image ngmodels.Image) error {
|
func(index int, image Image) error {
|
||||||
if len(image.URL) != 0 {
|
if len(image.URL) != 0 {
|
||||||
data.Alerts[index].ImageURL = image.URL
|
data.Alerts[index].ImageURL = image.URL
|
||||||
} else if len(image.Path) != 0 {
|
} else if len(image.Path) != 0 {
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package channels
|
package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/prometheus/alertmanager/template"
|
"github.com/prometheus/alertmanager/template"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FactoryConfig struct {
|
type FactoryConfig struct {
|
||||||
@ -19,10 +16,6 @@ type FactoryConfig struct {
|
|||||||
Template *template.Template
|
Template *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageStore interface {
|
|
||||||
GetImage(ctx context.Context, token string) (*models.Image, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFactoryConfig(config *NotificationChannelConfig, notificationService NotificationSender,
|
func NewFactoryConfig(config *NotificationChannelConfig, notificationService NotificationSender,
|
||||||
decryptFunc GetDecryptedValueFn, template *template.Template, imageStore ImageStore) (FactoryConfig, error) {
|
decryptFunc GetDecryptedValueFn, template *template.Template, imageStore ImageStore) (FactoryConfig, error) {
|
||||||
if config.Settings == nil {
|
if config.Settings == nil {
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -197,7 +196,7 @@ func (gcn *GoogleChatNotifier) buildScreenshotCard(ctx context.Context, alerts [
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = withStoredImages(ctx, gcn.log, gcn.images,
|
_ = withStoredImages(ctx, gcn.log, gcn.images,
|
||||||
func(index int, image ngmodels.Image) error {
|
func(index int, image Image) error {
|
||||||
if len(image.URL) == 0 {
|
if len(image.URL) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
26
pkg/services/ngalert/notifier/channels/images.go
Normal file
26
pkg/services/ngalert/notifier/channels/images.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrImageNotFound = errors.New("image not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
Token string
|
||||||
|
Path string
|
||||||
|
URL string
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Image) HasURL() bool {
|
||||||
|
return i.URL != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageStore interface {
|
||||||
|
GetImage(ctx context.Context, token string) (*Image, error)
|
||||||
|
}
|
@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// KafkaNotifier is responsible for sending
|
// KafkaNotifier is responsible for sending
|
||||||
@ -161,7 +160,7 @@ func buildState(as ...*types.Alert) models.AlertStateType {
|
|||||||
func buildContextImages(ctx context.Context, l log.Logger, imageStore ImageStore, as ...*types.Alert) []interface{} {
|
func buildContextImages(ctx context.Context, l log.Logger, imageStore ImageStore, as ...*types.Alert) []interface{} {
|
||||||
var contexts []interface{}
|
var contexts []interface{}
|
||||||
_ = withStoredImages(ctx, l, imageStore,
|
_ = withStoredImages(ctx, l, imageStore,
|
||||||
func(_ int, image ngmodels.Image) error {
|
func(_ int, image Image) error {
|
||||||
if image.URL != "" {
|
if image.URL != "" {
|
||||||
imageJSON := simplejson.New()
|
imageJSON := simplejson.New()
|
||||||
imageJSON.Set("type", "image")
|
imageJSON.Set("type", "image")
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -246,7 +245,7 @@ func (on *OpsgenieNotifier) buildOpsgenieMessage(ctx context.Context, alerts mod
|
|||||||
}
|
}
|
||||||
var images []string
|
var images []string
|
||||||
_ = withStoredImages(ctx, on.log, on.images,
|
_ = withStoredImages(ctx, on.log, on.images,
|
||||||
func(_ int, image ngmodels.Image) error {
|
func(_ int, image Image) error {
|
||||||
if len(image.URL) == 0 {
|
if len(image.URL) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -231,7 +230,7 @@ func (pn *PagerdutyNotifier) buildPagerdutyMessage(ctx context.Context, alerts m
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = withStoredImages(ctx, pn.log, pn.images,
|
_ = withStoredImages(ctx, pn.log, pn.images,
|
||||||
func(_ int, image ngmodels.Image) error {
|
func(_ int, image Image) error {
|
||||||
if len(image.URL) != 0 {
|
if len(image.URL) != 0 {
|
||||||
msg.Images = append(msg.Images, pagerDutyImage{Src: image.URL})
|
msg.Images = append(msg.Images, pagerDutyImage{Src: image.URL})
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -316,7 +315,7 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al
|
|||||||
func (pn *PushoverNotifier) writeImageParts(ctx context.Context, w *multipart.Writer, as ...*types.Alert) {
|
func (pn *PushoverNotifier) writeImageParts(ctx context.Context, w *multipart.Writer, as ...*types.Alert) {
|
||||||
// Pushover supports at most one image attachment with a maximum size of pushoverMaxFileSize.
|
// Pushover supports at most one image attachment with a maximum size of pushoverMaxFileSize.
|
||||||
// If the image is larger than pushoverMaxFileSize then return an error.
|
// If the image is larger than pushoverMaxFileSize then return an error.
|
||||||
_ = withStoredImages(ctx, pn.log, pn.images, func(index int, image ngmodels.Image) error {
|
_ = withStoredImages(ctx, pn.log, pn.images, func(index int, image Image) error {
|
||||||
f, err := os.Open(image.Path)
|
f, err := os.Open(image.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open the image: %w", err)
|
return fmt.Errorf("failed to open the image: %w", err)
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SensuGoNotifier struct {
|
type SensuGoNotifier struct {
|
||||||
@ -127,7 +126,7 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
|||||||
labels := make(map[string]string)
|
labels := make(map[string]string)
|
||||||
|
|
||||||
_ = withStoredImages(ctx, sn.log, sn.images,
|
_ = withStoredImages(ctx, sn.log, sn.images,
|
||||||
func(_ int, image ngmodels.Image) error {
|
func(_ int, image Image) error {
|
||||||
// If there is an image for this alert and the image has been uploaded
|
// 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
|
// to a public URL then add it to the request. We cannot add more than
|
||||||
// one image per request.
|
// one image per request.
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -220,7 +219,7 @@ func (sn *SlackNotifier) Notify(ctx context.Context, alerts ...*types.Alert) (bo
|
|||||||
|
|
||||||
// Do not upload images if using an incoming webhook as incoming webhooks cannot upload files
|
// Do not upload images if using an incoming webhook as incoming webhooks cannot upload files
|
||||||
if !isIncomingWebhook(sn.settings) {
|
if !isIncomingWebhook(sn.settings) {
|
||||||
if err := withStoredImages(ctx, sn.log, sn.images, func(index int, image ngmodels.Image) error {
|
if err := withStoredImages(ctx, sn.log, sn.images, func(index int, image Image) error {
|
||||||
// If we have exceeded the maximum number of images for this thread_ts
|
// If we have exceeded the maximum number of images for this thread_ts
|
||||||
// then tell the recipient and stop iterating subsequent images
|
// then tell the recipient and stop iterating subsequent images
|
||||||
if index >= maxImagesPerThreadTs {
|
if index >= maxImagesPerThreadTs {
|
||||||
@ -393,7 +392,7 @@ func (sn *SlackNotifier) createSlackMessage(ctx context.Context, alerts []*types
|
|||||||
|
|
||||||
if isIncomingWebhook(sn.settings) {
|
if isIncomingWebhook(sn.settings) {
|
||||||
// Incoming webhooks cannot upload files, instead share images via their URL
|
// Incoming webhooks cannot upload files, instead share images via their URL
|
||||||
_ = withStoredImages(ctx, sn.log, sn.images, func(index int, image ngmodels.Image) error {
|
_ = withStoredImages(ctx, sn.log, sn.images, func(index int, image Image) error {
|
||||||
if image.URL != "" {
|
if image.URL != "" {
|
||||||
req.Attachments[0].ImageURL = image.URL
|
req.Attachments[0].ImageURL = image.URL
|
||||||
return ErrImagesDone
|
return ErrImagesDone
|
||||||
@ -476,7 +475,7 @@ func (sn *SlackNotifier) sendSlackMessage(ctx context.Context, m *slackMessage)
|
|||||||
// createImageMultipart returns the mutlipart/form-data request and headers for files.upload.
|
// createImageMultipart returns the mutlipart/form-data request and headers for files.upload.
|
||||||
// It returns an error if the image does not exist or there was an error preparing the
|
// It returns an error if the image does not exist or there was an error preparing the
|
||||||
// multipart form.
|
// multipart form.
|
||||||
func (sn *SlackNotifier) createImageMultipart(image ngmodels.Image, channel, comment, thread_ts string) (http.Header, []byte, error) {
|
func (sn *SlackNotifier) createImageMultipart(image Image, channel, comment, thread_ts string) (http.Header, []byte, error) {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
w := multipart.NewWriter(&buf)
|
w := multipart.NewWriter(&buf)
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -553,7 +552,7 @@ func (sn *SlackNotifier) sendMultipart(ctx context.Context, headers http.Header,
|
|||||||
// uploadImage shares the image to the channel names or IDs. It returns an error if the file
|
// uploadImage shares the image to the channel names or IDs. It returns an error if the file
|
||||||
// does not exist, or if there was an error either preparing or sending the multipart/form-data
|
// does not exist, or if there was an error either preparing or sending the multipart/form-data
|
||||||
// request.
|
// request.
|
||||||
func (sn *SlackNotifier) uploadImage(ctx context.Context, image ngmodels.Image, channel, comment, thread_ts string) error {
|
func (sn *SlackNotifier) uploadImage(ctx context.Context, image Image, channel, comment, thread_ts string) error {
|
||||||
sn.log.Debug("Uploadimg image", "image", image.Token)
|
sn.log.Debug("Uploadimg image", "image", image.Token)
|
||||||
headers, data, err := sn.createImageMultipart(image, channel, comment, thread_ts)
|
headers, data, err := sn.createImageMultipart(image, channel, comment, thread_ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
@ -416,7 +415,7 @@ func setupSlackForTests(t *testing.T, settings string) (*SlackNotifier, *slackRe
|
|||||||
})
|
})
|
||||||
|
|
||||||
images := &fakeImageStore{
|
images := &fakeImageStore{
|
||||||
Images: []*models.Image{{
|
Images: []*Image{{
|
||||||
Token: "image-on-disk",
|
Token: "image-on-disk",
|
||||||
Path: f.Name(),
|
Path: f.Name(),
|
||||||
}, {
|
}, {
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -309,7 +308,7 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
|||||||
|
|
||||||
var s AdaptiveCardImageSetItem
|
var s AdaptiveCardImageSetItem
|
||||||
_ = withStoredImages(ctx, tn.log, tn.images,
|
_ = withStoredImages(ctx, tn.log, tn.images,
|
||||||
func(_ int, image ngmodels.Image) error {
|
func(_ int, image Image) error {
|
||||||
if image.URL != "" {
|
if image.URL != "" {
|
||||||
s.AppendImage(AdaptiveCardImageItem{URL: image.URL})
|
s.AppendImage(AdaptiveCardImageItem{URL: image.URL})
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -144,7 +143,7 @@ func (tn *TelegramNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the cmd to upload each image
|
// Create the cmd to upload each image
|
||||||
_ = withStoredImages(ctx, tn.log, tn.images, func(index int, image ngmodels.Image) error {
|
_ = withStoredImages(ctx, tn.log, tn.images, func(index int, image Image) error {
|
||||||
cmd, err = tn.newWebhookSyncCmd("sendPhoto", func(w *multipart.Writer) error {
|
cmd, err = tn.newWebhookSyncCmd("sendPhoto", func(w *multipart.Writer) error {
|
||||||
f, err := os.Open(image.Path)
|
f, err := os.Open(image.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -13,23 +13,22 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
"github.com/grafana/grafana/pkg/services/notifications"
|
"github.com/grafana/grafana/pkg/services/notifications"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeImageStore struct {
|
type fakeImageStore struct {
|
||||||
Images []*ngmodels.Image
|
Images []*Image
|
||||||
}
|
}
|
||||||
|
|
||||||
// getImage returns an image with the same token.
|
// getImage returns an image with the same token.
|
||||||
func (f *fakeImageStore) GetImage(_ context.Context, token string) (*ngmodels.Image, error) {
|
func (f *fakeImageStore) GetImage(_ context.Context, token string) (*Image, error) {
|
||||||
for _, img := range f.Images {
|
for _, img := range f.Images {
|
||||||
if img.Token == token {
|
if img.Token == token {
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, ngmodels.ErrImageNotFound
|
return nil, ErrImageNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFakeImageStore returns an image store with N test images.
|
// newFakeImageStore returns an image store with N test images.
|
||||||
@ -37,7 +36,7 @@ func (f *fakeImageStore) GetImage(_ context.Context, token string) (*ngmodels.Im
|
|||||||
func newFakeImageStore(n int) ImageStore {
|
func newFakeImageStore(n int) ImageStore {
|
||||||
s := fakeImageStore{}
|
s := fakeImageStore{}
|
||||||
for i := 1; i <= n; i++ {
|
for i := 1; i <= n; i++ {
|
||||||
s.Images = append(s.Images, &ngmodels.Image{
|
s.Images = append(s.Images, &Image{
|
||||||
Token: fmt.Sprintf("test-image-%d", i),
|
Token: fmt.Sprintf("test-image-%d", i),
|
||||||
URL: fmt.Sprintf("https://www.example.com/test-image-%d.jpg", i),
|
URL: fmt.Sprintf("https://www.example.com/test-image-%d.jpg", i),
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
@ -72,7 +71,7 @@ func newFakeImageStoreWithFile(t *testing.T, n int) ImageStore {
|
|||||||
t.Fatalf("failed to create test image: %s", err)
|
t.Fatalf("failed to create test image: %s", err)
|
||||||
}
|
}
|
||||||
files = append(files, file)
|
files = append(files, file)
|
||||||
s.Images = append(s.Images, &ngmodels.Image{
|
s.Images = append(s.Images, &Image{
|
||||||
Token: fmt.Sprintf("test-image-%d", i),
|
Token: fmt.Sprintf("test-image-%d", i),
|
||||||
Path: file,
|
Path: file,
|
||||||
URL: fmt.Sprintf("https://www.example.com/test-image-%d", i),
|
URL: fmt.Sprintf("https://www.example.com/test-image-%d", i),
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -158,7 +157,7 @@ func (tn *ThreemaNotifier) buildMessage(ctx context.Context, as ...*types.Alert)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = withStoredImages(ctx, tn.log, tn.images,
|
_ = withStoredImages(ctx, tn.log, tn.images,
|
||||||
func(_ int, image ngmodels.Image) error {
|
func(_ int, image Image) error {
|
||||||
if image.URL != "" {
|
if image.URL != "" {
|
||||||
message += fmt.Sprintf("*Image:* %s\n", image.URL)
|
message += fmt.Sprintf("*Image:* %s\n", image.URL)
|
||||||
}
|
}
|
||||||
|
@ -49,11 +49,11 @@ var (
|
|||||||
ErrImagesUnavailable = errors.New("alert screenshots are unavailable")
|
ErrImagesUnavailable = errors.New("alert screenshots are unavailable")
|
||||||
)
|
)
|
||||||
|
|
||||||
type forEachImageFunc func(index int, image models.Image) error
|
type forEachImageFunc func(index int, image Image) error
|
||||||
|
|
||||||
// getImage returns the image for the alert or an error. It returns a nil
|
// getImage returns the image for the alert or an error. It returns a nil
|
||||||
// image if the alert does not have an image token or the image does not exist.
|
// image if the alert does not have an image token or the image does not exist.
|
||||||
func getImage(ctx context.Context, l log.Logger, imageStore ImageStore, alert types.Alert) (*models.Image, error) {
|
func getImage(ctx context.Context, l log.Logger, imageStore ImageStore, alert types.Alert) (*Image, error) {
|
||||||
token := getTokenFromAnnotations(alert.Annotations)
|
token := getTokenFromAnnotations(alert.Annotations)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -63,7 +63,7 @@ func getImage(ctx context.Context, l log.Logger, imageStore ImageStore, alert ty
|
|||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
img, err := imageStore.GetImage(ctx, token)
|
img, err := imageStore.GetImage(ctx, token)
|
||||||
if errors.Is(err, models.ErrImageNotFound) || errors.Is(err, ErrImagesUnavailable) {
|
if errors.Is(err, ErrImageNotFound) || errors.Is(err, ErrImagesUnavailable) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
l.Warn("failed to get image with token", "token", token, "error", err)
|
l.Warn("failed to get image with token", "token", token, "error", err)
|
||||||
@ -107,7 +107,7 @@ func openImage(path string) (io.ReadCloser, error) {
|
|||||||
fp := filepath.Clean(path)
|
fp := filepath.Clean(path)
|
||||||
_, err := os.Stat(fp)
|
_, err := os.Stat(fp)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||||
return nil, models.ErrImageNotFound
|
return nil, ErrImageNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(fp)
|
f, err := os.Open(fp)
|
||||||
@ -128,7 +128,7 @@ func getTokenFromAnnotations(annotations model.LabelSet) string {
|
|||||||
type UnavailableImageStore struct{}
|
type UnavailableImageStore struct{}
|
||||||
|
|
||||||
// Get returns the image with the corresponding token, or ErrImageNotFound.
|
// Get returns the image with the corresponding token, or ErrImageNotFound.
|
||||||
func (u *UnavailableImageStore) GetImage(ctx context.Context, token string) (*models.Image, error) {
|
func (u *UnavailableImageStore) GetImage(ctx context.Context, token string) (*Image, error) {
|
||||||
return nil, ErrImagesUnavailable
|
return nil, ErrImagesUnavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ func TestWithStoredImages(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
imageStore := &fakeImageStore{Images: []*models.Image{{
|
imageStore := &fakeImageStore{Images: []*Image{{
|
||||||
Token: "test-image-1",
|
Token: "test-image-1",
|
||||||
URL: "https://www.example.com/test-image-1.jpg",
|
URL: "https://www.example.com/test-image-1.jpg",
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: time.Now().UTC(),
|
||||||
@ -45,7 +45,7 @@ func TestWithStoredImages(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// should iterate all images
|
// should iterate all images
|
||||||
err = withStoredImages(ctx, log.New(ctx), imageStore, func(index int, image models.Image) error {
|
err = withStoredImages(ctx, log.New(ctx), imageStore, func(index int, image Image) error {
|
||||||
i += 1
|
i += 1
|
||||||
return nil
|
return nil
|
||||||
}, alerts...)
|
}, alerts...)
|
||||||
@ -54,7 +54,7 @@ func TestWithStoredImages(t *testing.T) {
|
|||||||
|
|
||||||
// should iterate just the first image
|
// should iterate just the first image
|
||||||
i = 0
|
i = 0
|
||||||
err = withStoredImages(ctx, log.New(ctx), imageStore, func(index int, image models.Image) error {
|
err = withStoredImages(ctx, log.New(ctx), imageStore, func(index int, image Image) error {
|
||||||
i += 1
|
i += 1
|
||||||
return ErrImagesDone
|
return ErrImagesDone
|
||||||
}, alerts...)
|
}, alerts...)
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -139,7 +138,7 @@ func (vn *VictoropsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = withStoredImages(ctx, vn.log, vn.images,
|
_ = withStoredImages(ctx, vn.log, vn.images,
|
||||||
func(index int, image ngmodels.Image) error {
|
func(index int, image Image) error {
|
||||||
if image.URL != "" {
|
if image.URL != "" {
|
||||||
bodyJSON["image_url"] = image.URL
|
bodyJSON["image_url"] = image.URL
|
||||||
return ErrImagesDone
|
return ErrImagesDone
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const webexAPIURL = "https://webexapis.com/v1/messages"
|
const webexAPIURL = "https://webexapis.com/v1/messages"
|
||||||
@ -130,7 +129,7 @@ func (wn *WebexNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Augment our Alert data with ImageURLs if available.
|
// Augment our Alert data with ImageURLs if available.
|
||||||
_ = withStoredImages(ctx, wn.log, wn.images, func(index int, image ngmodels.Image) error {
|
_ = withStoredImages(ctx, wn.log, wn.images, func(index int, image Image) error {
|
||||||
// Cisco Webex only supports a single image per request: https://developer.webex.com/docs/basics#message-attachments
|
// Cisco Webex only supports a single image per request: https://developer.webex.com/docs/basics#message-attachments
|
||||||
if image.HasURL() {
|
if image.HasURL() {
|
||||||
data.Alerts[index].ImageURL = image.URL
|
data.Alerts[index].ImageURL = image.URL
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebhookNotifier is responsible for sending
|
// WebhookNotifier is responsible for sending
|
||||||
@ -160,7 +159,7 @@ func (wn *WebhookNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
|||||||
|
|
||||||
// Augment our Alert data with ImageURLs if available.
|
// Augment our Alert data with ImageURLs if available.
|
||||||
_ = withStoredImages(ctx, wn.log, wn.images,
|
_ = withStoredImages(ctx, wn.log, wn.images,
|
||||||
func(index int, image ngmodels.Image) error {
|
func(index int, image Image) error {
|
||||||
if len(image.URL) != 0 {
|
if len(image.URL) != 0 {
|
||||||
data.Alerts[index].ImageURL = image.URL
|
data.Alerts[index].ImageURL = image.URL
|
||||||
}
|
}
|
||||||
|
39
pkg/services/ngalert/notifier/images.go
Normal file
39
pkg/services/ngalert/notifier/images.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package notifier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type imageStore struct {
|
||||||
|
store store.ImageStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func newImageStore(store store.ImageStore) channels.ImageStore {
|
||||||
|
return &imageStore{
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i imageStore) GetImage(ctx context.Context, token string) (*channels.Image, error) {
|
||||||
|
image, err := i.store.GetImage(ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, models.ErrImageNotFound) {
|
||||||
|
err = channels.ErrImageNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result *channels.Image
|
||||||
|
if image != nil {
|
||||||
|
result = &channels.Image{
|
||||||
|
Token: image.Token,
|
||||||
|
Path: image.Path,
|
||||||
|
URL: image.URL,
|
||||||
|
CreatedAt: image.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user