From 2466685a41a8722335c3d81697b848fba31b50cd Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Fri, 28 Feb 2025 13:27:27 -0500 Subject: [PATCH] Alerting: Improve template testing by trying non-root scopes (#101471) Expand template testing to try additional scopes if the root scope fails. This mitigates errors for definitions like pagerduty.default.instances, which require the .Alerts scope. Added support for .Alerts and .Alert scopes. --- go.mod | 2 +- go.sum | 4 +- pkg/services/ngalert/api/api_alertmanager.go | 5 +- .../api/tooling/definitions/alertmanager.go | 13 ++++ .../ngalert/notifier/templates_test.go | 70 +++++++++++++++---- 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 +- 9 files changed, 83 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index e664a31975a..1ddd0152b12 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,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-20250224133628-2acbeef29642 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250225105729-99e678595501 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250224151205-5ef97131cc82 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 29c820b8964..21c166a08d9 100644 --- a/go.sum +++ b/go.sum @@ -1525,8 +1525,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-20250224133628-2acbeef29642 h1:iQ0h/h+QoguSZDF+ZpPxcM/C+m1kjh+aXjMpxywowPA= -github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2 h1:kESrzm0FcRVLmGIQCgl1MCwDGLH4sLzWphr7mcFdbfI= +github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250225105729-99e678595501 h1:FTuDRy/Shw8yOdG+v1DnkeuaCAl8fvwgcfaG9Wccuhg= github.com/grafana/authlib v0.0.0-20250225105729-99e678595501/go.mod h1:XVpdLhaeYqz414FmGnW00/0vTe1x8c0GRH3KaeRtyg0= github.com/grafana/authlib/types v0.0.0-20250224151205-5ef97131cc82 h1:DnRUYiAotHXnrfYJCvhH1NkiyWVcPm5Pd+P7Ugqt/d8= diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index 1d174a149c7..90c3843b65e 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -353,8 +353,9 @@ func newTestTemplateResult(res *notifier.TestTemplatesResults) apimodels.TestTem apiRes := apimodels.TestTemplatesResults{} for _, r := range res.Results { apiRes.Results = append(apiRes.Results, apimodels.TestTemplatesResult{ - Name: r.Name, - Text: r.Text, + Name: r.Name, + Text: r.Text, + Scope: apimodels.TemplateScope(r.Scope), }) } for _, e := range res.Errors { diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 84d29dcb249..5a9c6a5b868 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -369,6 +369,10 @@ type TestTemplatesResult struct { // Interpolated value of the template. Text string `json:"text"` + + // Scope that was successfully used to interpolate the template. If the root scope "." fails, more specific + // scopes will be tried, such as ".Alerts', or ".Alert". + Scope TemplateScope `json:"scope"` } type TestTemplatesErrorResult struct { @@ -390,6 +394,15 @@ const ( ExecutionError TemplateErrorKind = "execution_error" ) +// swagger:enum TemplateScope +type TemplateScope string + +const ( + RootScope TemplateScope = "." + AlertsScope TemplateScope = ".Alerts" + AlertScope TemplateScope = ".Alert" +) + // swagger:parameters RouteCreateSilence RouteCreateGrafanaSilence type CreateSilenceParams struct { // in:body diff --git a/pkg/services/ngalert/notifier/templates_test.go b/pkg/services/ngalert/notifier/templates_test.go index a5806405642..fb266ac15f9 100644 --- a/pkg/services/ngalert/notifier/templates_test.go +++ b/pkg/services/ngalert/notifier/templates_test.go @@ -53,8 +53,9 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val }, expected: TestTemplatesResults{ Results: []alertingNotify.TestTemplatesResult{{ - Name: "slack.title", - Text: "\nReceiver: TestReceiver\nStatus: firing\nExternalURL: http://localhost:9093\nAlerts: 1\nFiring Alerts: 1\nResolved Alerts: 0\nGroupLabels: group_label=group_label_value \nCommonLabels: alertname=alert1 grafana_folder=folder title lbl1=val1 \nCommonAnnotations: ann1=annv1 \n", + Name: "slack.title", + Text: "\nReceiver: TestReceiver\nStatus: firing\nExternalURL: http://localhost:9093\nAlerts: 1\nFiring Alerts: 1\nResolved Alerts: 0\nGroupLabels: group_label=group_label_value \nCommonLabels: alertname=alert1 grafana_folder=folder title lbl1=val1 \nCommonAnnotations: ann1=annv1 \n", + Scope: alertingNotify.TemplateScope(apimodels.RootScope), }}, Errors: nil, }, @@ -67,8 +68,9 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val }, expected: TestTemplatesResults{ Results: []alertingNotify.TestTemplatesResult{{ - Name: "slack.title", - Text: DefaultLabels[prometheusModel.AlertNameLabel], + Name: "slack.title", + Text: DefaultLabels[prometheusModel.AlertNameLabel], + Scope: alertingNotify.TemplateScope(apimodels.RootScope), }}, Errors: nil, }, @@ -81,8 +83,9 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val }, expected: TestTemplatesResults{ Results: []alertingNotify.TestTemplatesResult{{ - Name: "slack.title", - Text: DefaultLabels[alertingModels.FolderTitleLabel], + Name: "slack.title", + Text: DefaultLabels[alertingModels.FolderTitleLabel], + Scope: alertingNotify.TemplateScope(apimodels.RootScope), }}, Errors: nil, }, @@ -95,8 +98,9 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val }, expected: TestTemplatesResults{ Results: []alertingNotify.TestTemplatesResult{{ - Name: "slack.title", - Text: "B=22 C=1 ", + Name: "slack.title", + Text: "B=22 C=1 ", + Scope: alertingNotify.TemplateScope(apimodels.RootScope), }}, Errors: nil, }, @@ -109,8 +113,9 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val }, expected: TestTemplatesResults{ Results: []alertingNotify.TestTemplatesResult{{ - Name: "slack.title", - Text: DefaultAnnotations[alertingModels.ValueStringAnnotation], + Name: "slack.title", + Text: DefaultAnnotations[alertingModels.ValueStringAnnotation], + Scope: alertingNotify.TemplateScope(apimodels.RootScope), }}, Errors: nil, }, @@ -127,6 +132,7 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val Text: fmt.Sprintf("http://localhost:9093/d/%s?orgId=%s", DefaultAnnotations[alertingModels.DashboardUIDAnnotation], DefaultAnnotations[alertingModels.OrgIDAnnotation]), + Scope: alertingNotify.TemplateScope(apimodels.RootScope), }}, Errors: nil, }, @@ -144,6 +150,7 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val DefaultAnnotations[alertingModels.DashboardUIDAnnotation], DefaultAnnotations[alertingModels.OrgIDAnnotation], DefaultAnnotations[alertingModels.PanelIDAnnotation]), + Scope: alertingNotify.TemplateScope(apimodels.RootScope), }}, Errors: nil, }, @@ -156,8 +163,47 @@ CommonAnnotations: {{ range .CommonAnnotations.SortedPairs }}{{ .Name }}={{ .Val }, expected: TestTemplatesResults{ Results: []alertingNotify.TestTemplatesResult{{ - Name: "slack.title", - Text: fmt.Sprintf("http://localhost:3000?orgId=%s", DefaultAnnotations[alertingModels.OrgIDAnnotation]), + Name: "slack.title", + Text: fmt.Sprintf("http://localhost:3000?orgId=%s", DefaultAnnotations[alertingModels.OrgIDAnnotation]), + Scope: alertingNotify.TemplateScope(apimodels.RootScope), + }}, + Errors: nil, + }, + }, { + name: "Alerts scoped templated ", + input: apimodels.TestTemplatesConfigBodyParams{ + Alerts: []*amv2.PostableAlert{{Alert: amv2.Alert{GeneratorURL: "http://localhost:3000"}}}, + Name: "slack.title", + Template: `{{ define "slack.title" }} + {{ range . }} + Status: {{ .Status }} + Starts at: {{ .StartsAt }} + {{ end }} +{{ end }}`, + }, + expected: TestTemplatesResults{ + Results: []alertingNotify.TestTemplatesResult{{ + Name: "slack.title", + Text: "\n\t\n\t\tStatus: firing\n\t\tStarts at: 0001-01-01 00:00:00 +0000 UTC\n\t\n", + Scope: alertingNotify.TemplateScope(apimodels.AlertsScope), + }}, + Errors: nil, + }, + }, { + name: "Alert scoped templated ", + input: apimodels.TestTemplatesConfigBodyParams{ + Alerts: []*amv2.PostableAlert{{Alert: amv2.Alert{GeneratorURL: "http://localhost:3000"}}}, + Name: "slack.title", + Template: `{{ define "slack.title" }} + Status: {{ .Status }} + Starts at: {{ .StartsAt }} +{{ end }}`, + }, + expected: TestTemplatesResults{ + Results: []alertingNotify.TestTemplatesResult{{ + Name: "slack.title", + Text: "\n\tStatus: firing\n\tStarts at: 0001-01-01 00:00:00 +0000 UTC\n", + Scope: alertingNotify.TemplateScope(apimodels.AlertScope), }}, Errors: nil, }, diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index c515663008a..bc3820efab7 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-20250224133628-2acbeef29642 // indirect + github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2 // indirect github.com/grafana/authlib v0.0.0-20250225105729-99e678595501 // 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 11a44305649..a3a38f639b9 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -567,8 +567,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-20250224133628-2acbeef29642 h1:iQ0h/h+QoguSZDF+ZpPxcM/C+m1kjh+aXjMpxywowPA= -github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2 h1:kESrzm0FcRVLmGIQCgl1MCwDGLH4sLzWphr7mcFdbfI= +github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250225105729-99e678595501 h1:FTuDRy/Shw8yOdG+v1DnkeuaCAl8fvwgcfaG9Wccuhg= github.com/grafana/authlib v0.0.0-20250225105729-99e678595501/go.mod h1:XVpdLhaeYqz414FmGnW00/0vTe1x8c0GRH3KaeRtyg0= github.com/grafana/authlib/types v0.0.0-20250224151205-5ef97131cc82 h1:DnRUYiAotHXnrfYJCvhH1NkiyWVcPm5Pd+P7Ugqt/d8= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 75b69ced369..a6802a0d5b2 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-20250224133628-2acbeef29642 // indirect + github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2 // 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 83d766f41aa..49c224e0ad3 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -398,8 +398,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-20250224133628-2acbeef29642 h1:iQ0h/h+QoguSZDF+ZpPxcM/C+m1kjh+aXjMpxywowPA= -github.com/grafana/alerting v0.0.0-20250224133628-2acbeef29642/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= +github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2 h1:kESrzm0FcRVLmGIQCgl1MCwDGLH4sLzWphr7mcFdbfI= +github.com/grafana/alerting v0.0.0-20250225150117-15e285d78df2/go.mod h1:hdGB3dSl8Ma9Rjo2YiAEAjMkZ5HiNJbNDqRKDefRZrM= github.com/grafana/authlib v0.0.0-20250225105729-99e678595501 h1:FTuDRy/Shw8yOdG+v1DnkeuaCAl8fvwgcfaG9Wccuhg= github.com/grafana/authlib v0.0.0-20250225105729-99e678595501/go.mod h1:XVpdLhaeYqz414FmGnW00/0vTe1x8c0GRH3KaeRtyg0= github.com/grafana/authlib/types v0.0.0-20250224151205-5ef97131cc82 h1:DnRUYiAotHXnrfYJCvhH1NkiyWVcPm5Pd+P7Ugqt/d8=