mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Remove withStoredImage and change forEachFunc (#51384)
This commit is contained in:
parent
268ee678b3
commit
7fea330dc1
@ -102,10 +102,10 @@ func (n *AlertmanagerNotifier) Notify(ctx context.Context, as ...*types.Alert) (
|
||||
}
|
||||
|
||||
_ = withStoredImages(ctx, n.logger, n.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
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 include it as an annotation
|
||||
if image != nil && image.URL != "" {
|
||||
if image.URL != "" {
|
||||
as[index].Annotations["image"] = model.LabelValue(image.URL)
|
||||
}
|
||||
return nil
|
||||
|
@ -199,15 +199,11 @@ func (d DiscordNotifier) constructAttachments(ctx context.Context, as []*types.A
|
||||
attachments := make([]discordAttachment, 0)
|
||||
|
||||
_ = withStoredImages(ctx, d.log, d.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
func(index int, image ngmodels.Image) error {
|
||||
if embedQuota < 1 {
|
||||
return ErrImagesDone
|
||||
}
|
||||
|
||||
if image == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(image.URL) > 0 {
|
||||
attachments = append(attachments, discordAttachment{
|
||||
url: image.URL,
|
||||
|
@ -110,18 +110,16 @@ func (en *EmailNotifier) Notify(ctx context.Context, alerts ...*types.Alert) (bo
|
||||
// Extend alerts data with images, if available.
|
||||
var embeddedFiles []string
|
||||
_ = withStoredImages(ctx, en.log, en.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil {
|
||||
if len(image.URL) != 0 {
|
||||
data.Alerts[index].ImageURL = image.URL
|
||||
} else if len(image.Path) != 0 {
|
||||
_, err := os.Stat(image.Path)
|
||||
if err == nil {
|
||||
data.Alerts[index].EmbeddedImage = path.Base(image.Path)
|
||||
embeddedFiles = append(embeddedFiles, image.Path)
|
||||
} else {
|
||||
en.log.Warn("failed to get image file for email attachment", "file", image.Path, "err", err)
|
||||
}
|
||||
func(index int, image ngmodels.Image) error {
|
||||
if len(image.URL) != 0 {
|
||||
data.Alerts[index].ImageURL = image.URL
|
||||
} else if len(image.Path) != 0 {
|
||||
_, err := os.Stat(image.Path)
|
||||
if err == nil {
|
||||
data.Alerts[index].EmbeddedImage = path.Base(image.Path)
|
||||
embeddedFiles = append(embeddedFiles, image.Path)
|
||||
} else {
|
||||
en.log.Warn("failed to get image file for email attachment", "file", image.Path, "err", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -206,8 +206,8 @@ func (gcn *GoogleChatNotifier) buildScreenshotCard(ctx context.Context, alerts [
|
||||
}
|
||||
|
||||
_ = withStoredImages(ctx, gcn.log, gcn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image == nil || len(image.URL) == 0 {
|
||||
func(index int, image ngmodels.Image) error {
|
||||
if len(image.URL) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -107,8 +107,8 @@ func (kn *KafkaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
|
||||
var contexts []interface{}
|
||||
_ = withStoredImages(ctx, kn.log, kn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil && image.URL != "" {
|
||||
func(_ int, image ngmodels.Image) error {
|
||||
if image.URL != "" {
|
||||
imageJSON := simplejson.New()
|
||||
imageJSON.Set("type", "image")
|
||||
imageJSON.Set("src", image.URL)
|
||||
|
@ -242,8 +242,8 @@ func (on *OpsgenieNotifier) buildOpsgenieMessage(ctx context.Context, alerts mod
|
||||
|
||||
images := []string{}
|
||||
_ = withStoredImages(ctx, on.log, on.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image == nil || len(image.URL) == 0 {
|
||||
func(_ int, image ngmodels.Image) error {
|
||||
if len(image.URL) == 0 {
|
||||
return nil
|
||||
}
|
||||
images = append(images, image.URL)
|
||||
|
@ -189,8 +189,8 @@ func (pn *PagerdutyNotifier) buildPagerdutyMessage(ctx context.Context, alerts m
|
||||
}
|
||||
|
||||
_ = withStoredImages(ctx, pn.log, pn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil && len(image.URL) != 0 {
|
||||
func(_ int, image ngmodels.Image) error {
|
||||
if len(image.URL) != 0 {
|
||||
msg.Images = append(msg.Images, pagerDutyImage{Src: image.URL})
|
||||
}
|
||||
|
||||
|
@ -233,39 +233,36 @@ func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Al
|
||||
|
||||
// Pushover supports at most one image attachment with a maximum size of pushoverMaxFileSize.
|
||||
// If the image is larger than pushoverMaxFileSize then return an error.
|
||||
_ = withStoredImages(ctx, pn.log, pn.images, func(index int, image *ngmodels.Image) error {
|
||||
if image != nil {
|
||||
f, err := os.Open(image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open the image: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
pn.log.Error("failed to close the image", "file", image.Path)
|
||||
}
|
||||
}()
|
||||
|
||||
fileInfo, err := f.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stat the image: %w", err)
|
||||
}
|
||||
|
||||
if fileInfo.Size() > pushoverMaxFileSize {
|
||||
return fmt.Errorf("image would exceeded maximum file size: %d", fileInfo.Size())
|
||||
}
|
||||
|
||||
fw, err := w.CreateFormFile("attachment", image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create form file for the image: %w", err)
|
||||
}
|
||||
|
||||
if _, err = io.Copy(fw, f); err != nil {
|
||||
return fmt.Errorf("failed to copy the image to the form file: %w", err)
|
||||
}
|
||||
|
||||
return ErrImagesDone
|
||||
_ = withStoredImages(ctx, pn.log, pn.images, func(index int, image ngmodels.Image) error {
|
||||
f, err := os.Open(image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open the image: %w", err)
|
||||
}
|
||||
return nil
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
pn.log.Error("failed to close the image", "file", image.Path)
|
||||
}
|
||||
}()
|
||||
|
||||
fileInfo, err := f.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stat the image: %w", err)
|
||||
}
|
||||
|
||||
if fileInfo.Size() > pushoverMaxFileSize {
|
||||
return fmt.Errorf("image would exceeded maximum file size: %d", fileInfo.Size())
|
||||
}
|
||||
|
||||
fw, err := w.CreateFormFile("attachment", image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create form file for the image: %w", err)
|
||||
}
|
||||
|
||||
if _, err = io.Copy(fw, f); err != nil {
|
||||
return fmt.Errorf("failed to copy the image to the form file: %w", err)
|
||||
}
|
||||
|
||||
return ErrImagesDone
|
||||
}, as...)
|
||||
|
||||
var sound string
|
||||
|
@ -139,21 +139,17 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
||||
|
||||
labels := make(map[string]string)
|
||||
|
||||
var imageURL string
|
||||
_ = withStoredImages(ctx, sn.log, sn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
func(_ 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
|
||||
if image.URL != "" {
|
||||
labels["imageURL"] = image.URL
|
||||
return ErrImagesDone
|
||||
}
|
||||
return nil
|
||||
}, as...)
|
||||
if imageURL != "" {
|
||||
labels["imageURL"] = imageURL
|
||||
}
|
||||
|
||||
ruleURL := joinUrlPath(sn.tmpl.ExternalURL.String(), "/alerting/list", sn.log)
|
||||
labels["ruleURL"] = ruleURL
|
||||
|
@ -319,15 +319,10 @@ func (sn *SlackNotifier) buildSlackMessage(ctx context.Context, alrts []*types.A
|
||||
},
|
||||
}
|
||||
|
||||
_ = withStoredImage(ctx, sn.log, sn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil {
|
||||
req.Attachments[0].ImageURL = image.URL
|
||||
return ErrImagesDone
|
||||
}
|
||||
return nil
|
||||
},
|
||||
0, alrts...)
|
||||
_ = withStoredImages(ctx, sn.log, sn.images, func(index int, image ngmodels.Image) error {
|
||||
req.Attachments[0].ImageURL = image.URL
|
||||
return ErrImagesDone
|
||||
}, alrts...)
|
||||
|
||||
if tmplErr != nil {
|
||||
sn.log.Warn("failed to template Slack message", "err", tmplErr.Error())
|
||||
|
@ -95,8 +95,8 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
|
||||
images := []teamsImage{}
|
||||
_ = withStoredImages(ctx, tn.log, tn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil && len(image.URL) != 0 {
|
||||
func(_ int, image ngmodels.Image) error {
|
||||
if len(image.URL) != 0 {
|
||||
images = append(images, teamsImage{Image: image.URL})
|
||||
}
|
||||
return nil
|
||||
|
@ -117,33 +117,31 @@ func (tn *TelegramNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
|
||||
}
|
||||
|
||||
// Create the cmd to upload each image
|
||||
_ = withStoredImages(ctx, tn.log, tn.images, func(index int, image *ngmodels.Image) error {
|
||||
if image != nil {
|
||||
cmd, err = tn.newWebhookSyncCmd("sendPhoto", func(w *multipart.Writer) error {
|
||||
f, err := os.Open(image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open image: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
tn.log.Warn("failed to close image", "err", err)
|
||||
}
|
||||
}()
|
||||
fw, err := w.CreateFormFile("photo", image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create form file: %w", err)
|
||||
}
|
||||
if _, err := io.Copy(fw, f); err != nil {
|
||||
return fmt.Errorf("failed to write to form file: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
_ = withStoredImages(ctx, tn.log, tn.images, func(index int, image ngmodels.Image) error {
|
||||
cmd, err = tn.newWebhookSyncCmd("sendPhoto", func(w *multipart.Writer) error {
|
||||
f, err := os.Open(image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create image: %w", err)
|
||||
return fmt.Errorf("failed to open image: %w", err)
|
||||
}
|
||||
if err := tn.ns.SendWebhookSync(ctx, cmd); err != nil {
|
||||
return fmt.Errorf("failed to upload image to telegram: %w", err)
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
tn.log.Warn("failed to close image", "err", err)
|
||||
}
|
||||
}()
|
||||
fw, err := w.CreateFormFile("photo", image.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create form file: %w", err)
|
||||
}
|
||||
if _, err := io.Copy(fw, f); err != nil {
|
||||
return fmt.Errorf("failed to write to form file: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create image: %w", err)
|
||||
}
|
||||
if err := tn.ns.SendWebhookSync(ctx, cmd); err != nil {
|
||||
return fmt.Errorf("failed to upload image to telegram: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, as...)
|
||||
|
@ -132,8 +132,8 @@ func (tn *ThreemaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
||||
)
|
||||
|
||||
_ = withStoredImages(ctx, tn.log, tn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil && image.URL != "" {
|
||||
func(_ int, image ngmodels.Image) error {
|
||||
if image.URL != "" {
|
||||
message += fmt.Sprintf("*Image:* %s\n", image.URL)
|
||||
}
|
||||
return nil
|
||||
|
@ -46,57 +46,51 @@ var (
|
||||
ErrImagesUnavailable = errors.New("alert screenshots are unavailable")
|
||||
)
|
||||
|
||||
// For each alert, attempts to load the models.Image for an image token
|
||||
// associated with the alert, then calls forEachFunc with the index of the
|
||||
// alert and the retrieved image struct. If there is no image token, or the
|
||||
// image does not exist, forEachFunc will be called with a nil value for the
|
||||
// image. If forEachFunc returns an error, withStoredImages will return
|
||||
// immediately. If there is a runtime error retrieving images from the image
|
||||
// store, withStoredImages will attempt to continue executing, after logging
|
||||
// a warning.
|
||||
func withStoredImages(ctx context.Context, l log.Logger, imageStore ImageStore, forEachFunc func(index int, image *models.Image) error, alerts ...*types.Alert) error {
|
||||
for i := range alerts {
|
||||
err := withStoredImage(ctx, l, imageStore, forEachFunc, i, alerts...)
|
||||
if err != nil {
|
||||
// Stop iteration as forEachFunc has found the intended image or
|
||||
// iterated the maximum number of images
|
||||
if errors.Is(err, ErrImagesDone) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
type forEachImageFunc func(index int, image models.Image) error
|
||||
|
||||
// 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.
|
||||
func getImage(ctx context.Context, l log.Logger, imageStore ImageStore, alert types.Alert) (*models.Image, error) {
|
||||
token := getTokenFromAnnotations(alert.Annotations)
|
||||
if token == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(ctx, ImageStoreTimeout)
|
||||
defer cancelFunc()
|
||||
|
||||
img, err := imageStore.GetImage(ctx, token)
|
||||
if errors.Is(err, models.ErrImageNotFound) || errors.Is(err, ErrImagesUnavailable) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
l.Warn("failed to get image with token", "token", token, "err", err)
|
||||
return nil, err
|
||||
} else {
|
||||
return img, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func withStoredImage(ctx context.Context, l log.Logger, imageStore ImageStore, imageFunc func(index int, image *models.Image) error, index int, alerts ...*types.Alert) error {
|
||||
imgToken := getTokenFromAnnotations(alerts[index].Annotations)
|
||||
if len(imgToken) == 0 {
|
||||
err := imageFunc(index, nil)
|
||||
// withStoredImages retrieves the image for each alert and then calls forEachFunc
|
||||
// with the index of the alert and the retrieved image struct. If the alert does
|
||||
// not have an image token, or the image does not exist then forEachFunc will not be
|
||||
// called for that alert. If forEachFunc returns an error, withStoredImages will return
|
||||
// the error and not iterate the remaining alerts. A forEachFunc can return ErrImagesDone
|
||||
// to stop the iteration of remaining alerts if the intended image or maximum number of
|
||||
// images have been found.
|
||||
func withStoredImages(ctx context.Context, l log.Logger, imageStore ImageStore, forEachFunc forEachImageFunc, alerts ...*types.Alert) error {
|
||||
for index, alert := range alerts {
|
||||
img, err := getImage(ctx, l, imageStore, *alert)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if img != nil {
|
||||
if err := forEachFunc(index, *img); err != nil {
|
||||
if errors.Is(err, ErrImagesDone) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, ImageStoreTimeout)
|
||||
img, err := imageStore.GetImage(timeoutCtx, imgToken)
|
||||
cancel()
|
||||
|
||||
if errors.Is(err, models.ErrImageNotFound) || errors.Is(err, ErrImagesUnavailable) {
|
||||
err := imageFunc(index, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
// Ignore errors. Don't log "ImageUnavailable", which means the storage doesn't exist.
|
||||
l.Warn("failed to retrieve image url from store", "err", err)
|
||||
}
|
||||
|
||||
err = imageFunc(index, img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ func TestWithStoredImages(t *testing.T) {
|
||||
)
|
||||
|
||||
// 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 models.Image) error {
|
||||
i += 1
|
||||
return nil
|
||||
}, alerts...)
|
||||
@ -54,7 +54,7 @@ func TestWithStoredImages(t *testing.T) {
|
||||
|
||||
// should iterate just the first image
|
||||
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 models.Image) error {
|
||||
i += 1
|
||||
return ErrImagesDone
|
||||
}, alerts...)
|
||||
|
@ -119,8 +119,8 @@ func (vn *VictoropsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bo
|
||||
bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion)
|
||||
|
||||
_ = withStoredImages(ctx, vn.log, vn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil && image.URL != "" {
|
||||
func(index int, image ngmodels.Image) error {
|
||||
if image.URL != "" {
|
||||
bodyJSON.Set("image_url", image.URL)
|
||||
return ErrImagesDone
|
||||
}
|
||||
|
@ -120,8 +120,8 @@ func (wn *WebhookNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
||||
|
||||
// Augment our Alert data with ImageURLs if available.
|
||||
_ = withStoredImages(ctx, wn.log, wn.images,
|
||||
func(index int, image *ngmodels.Image) error {
|
||||
if image != nil && len(image.URL) != 0 {
|
||||
func(index int, image ngmodels.Image) error {
|
||||
if len(image.URL) != 0 {
|
||||
data.Alerts[index].ImageURL = image.URL
|
||||
}
|
||||
return nil
|
||||
|
Loading…
Reference in New Issue
Block a user