From b78a63b0ad2ed51f4c679ed144cf40b21037a230 Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Thu, 20 Feb 2025 12:47:40 -0500 Subject: [PATCH] Alerting: Use new image TokenProvider and send image url in annotation (#99989) * Send new annotation containing image url * Use new image TokenProvider with TokenStore New abstraction GetImage no longer needs to support parsing both token and url from annotations, as remote AM will use the new URLProvider. Instead, we use the new generic TokenProvider and give it a TokenStore backed by the grafana database. That means we revert back to always using token simplifying code and security considerations. * Upgrade grafana/alerting to merged commit SHA --- go.mod | 2 +- go.sum | 4 +- pkg/services/ngalert/models/image.go | 5 +- pkg/services/ngalert/notifier/images.go | 140 +++-------- pkg/services/ngalert/notifier/images_test.go | 230 +++++++------------ pkg/services/ngalert/notifier/log.go | 4 + pkg/services/ngalert/state/compat.go | 15 +- pkg/services/ngalert/state/compat_test.go | 13 +- pkg/storage/unified/apistore/go.mod | 2 +- pkg/storage/unified/apistore/go.sum | 4 +- pkg/storage/unified/resource/go.mod | 2 +- pkg/storage/unified/resource/go.sum | 4 +- 12 files changed, 148 insertions(+), 277 deletions(-) diff --git a/go.mod b/go.mod index bbbee0fabf9..99f3d09f9fb 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250219142948-d43046431703 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 5bd68002377..839d2e19349 100644 --- a/go.sum +++ b/go.sum @@ -1511,8 +1511,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250219142948-d43046431703 h1:Z53rmFhUeif1/cFjOQqxHyv+YfXac4vzyl9yzdjxOoY= -github.com/grafana/alerting v0.0.0-20250219142948-d43046431703/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 h1:LGH+tVzHCDrR9hsltmkP4jmNRg5IreQw5CNFbJKlnts= +github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= diff --git a/pkg/services/ngalert/models/image.go b/pkg/services/ngalert/models/image.go index 07c8578cdd9..1a90090aa53 100644 --- a/pkg/services/ngalert/models/image.go +++ b/pkg/services/ngalert/models/image.go @@ -6,8 +6,11 @@ import ( ) var ( - // ErrImageNotFound is returned when the image does not exist. + // ErrImageNotFound is returned when the image does not exist or is expired. ErrImageNotFound = errors.New("image not found") + + // ErrImageDataUnavailable is returned when image data is unavailable. Usually because the image is missing a path. + ErrImageDataUnavailable = errors.New("image data is unavailable") ) type Image struct { diff --git a/pkg/services/ngalert/notifier/images.go b/pkg/services/ngalert/notifier/images.go index d8effb176ce..d25b20bce80 100644 --- a/pkg/services/ngalert/notifier/images.go +++ b/pkg/services/ngalert/notifier/images.go @@ -6,145 +6,61 @@ import ( "io" "os" "path/filepath" - "strings" 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/store" ) -type imageProvider struct { +type tokenStore struct { store store.ImageStore logger log.Logger } +var _ alertingImages.TokenStore = (*tokenStore)(nil) + func newImageProvider(store store.ImageStore, logger log.Logger) alertingImages.Provider { - return &imageProvider{ + return alertingImages.NewTokenProvider(&tokenStore{ store: store, logger: logger, - } + }, newLogWrapper(logger)) } -func (i imageProvider) GetImage(ctx context.Context, uri string) (*alertingImages.Image, error) { - image, err := i.getImageFromURI(ctx, uri) +func (t tokenStore) GetImage(ctx context.Context, token string) (*alertingImages.Image, error) { + image, err := t.store.GetImage(ctx, token) if err != nil { if errors.Is(err, models.ErrImageNotFound) { - i.logger.Info("Image not found in database", "uri", uri) return nil, alertingImages.ErrImageNotFound } return nil, err } return &alertingImages.Image{ - Token: image.Token, - Path: image.Path, - URL: image.URL, - CreatedAt: image.CreatedAt, + URL: image.URL, + RawData: func(_ context.Context) (alertingImages.ImageContent, error) { + if image.Path == "" { + return alertingImages.ImageContent{}, models.ErrImageDataUnavailable + } + b, err := readImage(image.Path, t.logger) + if err != nil { + return alertingImages.ImageContent{}, err + } + return alertingImages.ImageContent{ + Name: filepath.Base(image.Path), + Content: b, + }, 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) { +// readImage returns an image from the given path. +func readImage(path string, logger log.Logger) ([]byte, error) { fp := filepath.Clean(path) _, err := os.Stat(fp) if os.IsNotExist(err) || os.IsPermission(err) { - return nil, alertingImages.ErrImageNotFound + return nil, models.ErrImageNotFound } f, err := os.Open(fp) @@ -152,5 +68,11 @@ func openImage(path string) (io.ReadCloser, error) { return nil, err } - return f, nil + defer func() { + if err := f.Close(); err != nil { + logger.Error("Failed to close image file", "error", err) + } + }() + + return io.ReadAll(f) } diff --git a/pkg/services/ngalert/notifier/images_test.go b/pkg/services/ngalert/notifier/images_test.go index b16bd0e6360..85ed780cb3b 100644 --- a/pkg/services/ngalert/notifier/images_test.go +++ b/pkg/services/ngalert/notifier/images_test.go @@ -2,7 +2,6 @@ package notifier import ( "context" - "io" "os" "path/filepath" "testing" @@ -11,177 +10,98 @@ import ( alertingImages "github.com/grafana/alerting/images" alertingModels "github.com/grafana/alerting/models" alertingNotify "github.com/grafana/alerting/notify" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/prometheus/common/model" - "github.com/stretchr/testify/require" ) func TestGetImage(t *testing.T) { - fakeImageStore := store.NewFakeImageStore(t) - store := newImageProvider(fakeImageStore, log.NewNopLogger()) + testBytes := []byte("some test bytes") + testPath := generateTestFile(t, testBytes) - t.Run("queries by token when it gets a token", func(tt *testing.T) { - img := models.Image{ - Token: "test", - URL: "http://localhost:1234", - Path: "test.png", - } - err := fakeImageStore.SaveImage(context.Background(), &img) - require.NoError(tt, err) - - // nolint:staticcheck - savedImg, err := store.GetImage(context.Background(), "token://"+img.Token) - require.NoError(tt, err) - require.Equal(tt, savedImg.Token, img.Token) - require.Equal(tt, savedImg.URL, img.URL) - require.Equal(tt, savedImg.Path, img.Path) - }) - - t.Run("queries by URL when it gets a URL", func(tt *testing.T) { - img := models.Image{ - Token: "test", - Path: "test.png", - URL: "https://test.com/test.png", - } - err := fakeImageStore.SaveImage(context.Background(), &img) - require.NoError(tt, err) - - // nolint:staticcheck - savedImg, err := store.GetImage(context.Background(), img.URL) - require.NoError(tt, err) - require.Equal(tt, savedImg.Token, img.Token) - require.Equal(tt, savedImg.URL, img.URL) - 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", + imageWithoutPath = models.Image{ + Token: "test-token-no-path", 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", + Token: "test-token", URL: "https://test.com", Path: testPath, CreatedAt: time.Now().UTC(), ExpiresAt: time.Now().UTC().Add(24 * time.Hour), } + testImageMissingFile = models.Image{ + Token: "test-token-missing-file", + URL: "https://test.com", + Path: "/tmp/missing/1234asdf.png", + CreatedAt: time.Now().UTC(), + ExpiresAt: time.Now().UTC().Add(24 * time.Hour), + } ) - fakeImageStore := store.NewFakeImageStore(t, &imageWithoutPath, &testImage) + fakeImageStore := store.NewFakeImageStore(t, &imageWithoutPath, &testImage, &testImageMissingFile) store := newImageProvider(fakeImageStore, log.NewNopLogger()) tests := []struct { - name string - uri string - expFilename string - expBytes []byte - expErr error + name string + token string + url string + expImage *alertingImages.Image + expImageContent *alertingImages.ImageContent + expRawDataErr error }{ { - "URL does not exist", - "https://invalid.com/test", - "", - nil, - alertingImages.ErrImageNotFound, + name: "Given existing raw token, expect image", + token: testImage.Token, + expImage: &alertingImages.Image{ + URL: testImage.URL, + }, }, { - "existing URL", - testImage.URL, - filepath.Base(testPath), - testBytes, - nil, + name: "Given existing token and url, expect image", + token: testImage.Token, + url: testImage.URL, + expImage: &alertingImages.Image{ + URL: testImage.URL, + }, }, { - "token does not exist", - "token://invalid", - "", - nil, - alertingImages.ErrImageNotFound, + name: "Given existing with just url, expect nil", + token: "", + url: testImage.URL, + expImage: nil, }, { - "existing token", - "token://" + testImage.Token, - filepath.Base(testPath), - testBytes, - nil, + name: "Given missing raw token, expect nil", + token: "invalid", + expImage: nil, }, { - "image has no path", - "token://" + imageWithoutPath.Token, - "", - nil, - alertingImages.ErrImagesNoPath, + name: "Given image with Path, expect RawData", + token: testImage.Token, + expImage: &alertingImages.Image{ + URL: testImage.URL, + }, + expImageContent: &alertingImages.ImageContent{ + Name: filepath.Base(testImage.Path), + Content: testBytes, + }, + }, { + name: "Given image with Path but file doesn't exist, expect RawData error", + token: testImageMissingFile.Token, + expImage: &alertingImages.Image{ + URL: testImageMissingFile.URL, + }, + expRawDataErr: models.ErrImageNotFound, + }, { + name: "Given image without Path, expect RawData error", + token: imageWithoutPath.Token, + expImage: &alertingImages.Image{ + URL: imageWithoutPath.URL, + }, + expRawDataErr: models.ErrImageDataUnavailable, }, } @@ -189,18 +109,24 @@ func TestGetRawImage(t *testing.T) { t.Run(test.name, func(tt *testing.T) { alert := alertingNotify.Alert{ Alert: model.Alert{ - Annotations: model.LabelSet{alertingModels.ImageTokenAnnotation: model.LabelValue(test.uri)}, + Annotations: model.LabelSet{alertingModels.ImageTokenAnnotation: model.LabelValue(test.token)}, }, } - 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) + image, err := store.GetImage(context.Background(), alert) + require.NoError(tt, err) + if test.expImage == nil { + require.Nil(tt, image) + return + } + require.Equal(tt, test.expImage.URL, image.URL) + if test.expImageContent != nil { + ic, err := image.RawData(context.Background()) require.NoError(tt, err) - require.Equal(tt, test.expBytes, b) - require.NoError(t, readCloser.Close()) + require.Equal(tt, *test.expImageContent, ic) + } + if test.expRawDataErr != nil { + _, err := image.RawData(context.Background()) + require.ErrorIs(tt, err, test.expRawDataErr) } }) } diff --git a/pkg/services/ngalert/notifier/log.go b/pkg/services/ngalert/notifier/log.go index c9dc97ea413..4aef94c49ed 100644 --- a/pkg/services/ngalert/notifier/log.go +++ b/pkg/services/ngalert/notifier/log.go @@ -10,6 +10,10 @@ var LoggerFactory alertingLogging.LoggerFactory = func(logger string, ctx ...any return &logWrapper{log.New(append([]any{logger}, ctx...)...)} } +func newLogWrapper(logger log.Logger, ctx ...any) alertingLogging.Logger { + return &logWrapper{logger.New(ctx...)} +} + type logWrapper struct { *log.ConcreteLogger } diff --git a/pkg/services/ngalert/state/compat.go b/pkg/services/ngalert/state/compat.go index 55e1fdf76fe..7f4b5c41428 100644 --- a/pkg/services/ngalert/state/compat.go +++ b/pkg/services/ngalert/state/compat.go @@ -17,6 +17,7 @@ import ( apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/eval" + ngModels "github.com/grafana/grafana/pkg/services/ngalert/models" ) const ( @@ -48,8 +49,8 @@ func StateToPostableAlert(transition StateTransition, appURL *url.URL) *models.P nA[alertingModels.ValueStringAnnotation] = alertState.LastEvaluationString } - if alertState.Image != nil && alertState.Image.Token != "" { - nA[alertingModels.ImageTokenAnnotation] = alertState.Image.Token + if alertState.Image != nil { + attachImageAnnotations(alertState.Image, nA) } if alertState.StateReason != "" { @@ -153,3 +154,13 @@ func FromAlertsStateToStoppedAlert(firingStates []StateTransition, appURL *url.U } return alerts } + +// attachImageAnnotations attaches image annotations to the alert. +func attachImageAnnotations(image *ngModels.Image, a data.Labels) { + if image.Token != "" { + a[alertingModels.ImageTokenAnnotation] = image.Token + } + if image.URL != "" { + a[alertingModels.ImageURLAnnotation] = image.URL + } +} diff --git a/pkg/services/ngalert/state/compat_test.go b/pkg/services/ngalert/state/compat_test.go index bd0189badb8..84d5ad8e6fa 100644 --- a/pkg/services/ngalert/state/compat_test.go +++ b/pkg/services/ngalert/state/compat_test.go @@ -120,10 +120,10 @@ func Test_StateToPostableAlert(t *testing.T) { require.Equal(t, expected, result.Annotations) }) - t.Run("add __alertImageToken__ if there is an image token", func(t *testing.T) { + t.Run("add both annotations if there is an image token and url", func(t *testing.T) { alertState := randomTransition(eval.Normal, tc.state) alertState.Annotations = randomMapOfStrings() - alertState.Image = &ngModels.Image{Token: "test_token"} + alertState.Image = &ngModels.Image{Token: "test_token", URL: "test_url"} result := StateToPostableAlert(alertState, appURL) @@ -131,12 +131,17 @@ func Test_StateToPostableAlert(t *testing.T) { for k, v := range alertState.Annotations { expected[k] = v } - expected["__alertImageToken__"] = alertState.Image.Token + expected[alertingModels.ImageTokenAnnotation] = alertState.Image.Token + expected[alertingModels.ImageURLAnnotation] = alertState.Image.URL + + // Sanity check that the annotation is correct. + require.Contains(t, result.Annotations[alertingModels.ImageTokenAnnotation], alertState.Image.Token) + require.Contains(t, result.Annotations[alertingModels.ImageURLAnnotation], alertState.Image.URL) require.Equal(t, expected, result.Annotations) }) - t.Run("don't add __alertImageToken__ if there's no image token", func(t *testing.T) { + t.Run("don't add annotations if there's no image token or url", func(t *testing.T) { alertState := randomTransition(eval.Normal, tc.state) alertState.Annotations = randomMapOfStrings() alertState.Image = &ngModels.Image{} diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index e758daa2d06..16b8b6442de 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -192,7 +192,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/grafana/alerting v0.0.0-20250219142948-d43046431703 // indirect + github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 // indirect github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index fa94a7e92ec..faf5e7a9e0f 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -566,8 +566,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250219142948-d43046431703 h1:Z53rmFhUeif1/cFjOQqxHyv+YfXac4vzyl9yzdjxOoY= -github.com/grafana/alerting v0.0.0-20250219142948-d43046431703/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 h1:LGH+tVzHCDrR9hsltmkP4jmNRg5IreQw5CNFbJKlnts= +github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 28745ab869d..a7d81ec25ca 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -117,7 +117,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20250219142948-d43046431703 // indirect + github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect github.com/grafana/grafana-aws-sdk v0.31.5 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 09e3a20b8a6..9ee7f749839 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -397,8 +397,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/alerting v0.0.0-20250219142948-d43046431703 h1:Z53rmFhUeif1/cFjOQqxHyv+YfXac4vzyl9yzdjxOoY= -github.com/grafana/alerting v0.0.0-20250219142948-d43046431703/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0 h1:LGH+tVzHCDrR9hsltmkP4jmNRg5IreQw5CNFbJKlnts= +github.com/grafana/alerting v0.0.0-20250219153626-c475b1a572b0/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4= github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4= github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA=