Alerting: Do not include button in googlechat notification if URL invalid (#47317)

* Alerting: Do not include button in googlechat notification if URL invalid

* Apply suggestions from code review

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>

* Alerting: Add test case for invalid external URL in googlechat notifier

Co-authored-by: Alexander Weaver <weaver.alex.d@gmail.com>
pull/49535/head
Johannes Hertenstein 3 years ago committed by GitHub
parent bae9a60089
commit 16d738a03a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      pkg/services/alerting/notifiers/googlechat.go
  2. 15
      pkg/services/ngalert/notifier/channels/googlechat.go
  3. 107
      pkg/services/ngalert/notifier/channels/googlechat_test.go

@ -3,6 +3,7 @@ package notifiers
import (
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/grafana/grafana/pkg/infra/log"
@ -171,6 +172,7 @@ func (gcn *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
}
}
if gcn.isUrlAbsolute(ruleURL) {
// add a button widget (link to Grafana)
widgets = append(widgets, buttonWidget{
Buttons: []button{
@ -186,6 +188,9 @@ func (gcn *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
},
},
})
} else {
gcn.log.Warn("Grafana External URL setting is missing or invalid. Skipping 'open in grafana' button to prevent google from displaying empty alerts.", "ruleURL", ruleURL)
}
// add text paragraph widget for the build version and timestamp
widgets = append(widgets, textParagraphWidget{
@ -227,3 +232,13 @@ func (gcn *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
return nil
}
func (gcn *GoogleChatNotifier) isUrlAbsolute(urlToCheck string) bool {
parsed, err := url.Parse(urlToCheck)
if err != nil {
gcn.log.Warn("Could not parse URL", "urlToCheck", urlToCheck)
return false
}
return parsed.IsAbs()
}

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"time"
"github.com/prometheus/alertmanager/template"
@ -101,6 +102,7 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
}
ruleURL := joinUrlPath(gcn.tmpl.ExternalURL.String(), "/alerting/list", gcn.log)
if gcn.isUrlAbsolute(ruleURL) {
// Add a button widget (link to Grafana).
widgets = append(widgets, buttonWidget{
Buttons: []button{
@ -116,6 +118,9 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
},
},
})
} else {
gcn.log.Warn("Grafana External URL setting is missing or invalid. Skipping 'open in grafana' button to prevent google from displaying empty alerts.", "ruleURL", ruleURL)
}
// Add text paragraph widget for the build version and timestamp.
widgets = append(widgets, textParagraphWidget{
@ -182,6 +187,16 @@ func (gcn *GoogleChatNotifier) SendResolved() bool {
return !gcn.GetDisableResolveMessage()
}
func (gcn *GoogleChatNotifier) isUrlAbsolute(urlToCheck string) bool {
parsed, err := url.Parse(urlToCheck)
if err != nil {
gcn.log.Warn("Could not parse URL", "urlToCheck", urlToCheck)
return false
}
return parsed.IsAbs()
}
func (gcn *GoogleChatNotifier) buildScreenshotCard(ctx context.Context, alerts []*types.Alert) *card {
card := card{
Header: header{

@ -20,12 +20,6 @@ func TestGoogleChatNotifier(t *testing.T) {
constNow := time.Now()
defer mockTimeNow(constNow)()
tmpl := templateForTests(t)
externalURL, err := url.Parse("http://localhost")
require.NoError(t, err)
tmpl.ExternalURL = externalURL
cases := []struct {
name string
settings string
@ -33,10 +27,12 @@ func TestGoogleChatNotifier(t *testing.T) {
expMsg *outerStruct
expInitError string
expMsgError error
externalURL string
}{
{
name: "One alert",
settings: `{"url": "http://localhost"}`,
externalURL: "http://localhost",
alerts: []*types.Alert{
{
Alert: model.Alert{
@ -91,6 +87,7 @@ func TestGoogleChatNotifier(t *testing.T) {
}, {
name: "Multiple alerts",
settings: `{"url": "http://localhost"}`,
externalURL: "http://localhost",
alerts: []*types.Alert{
{
Alert: model.Alert{
@ -149,10 +146,12 @@ func TestGoogleChatNotifier(t *testing.T) {
}, {
name: "Error in initing",
settings: `{}`,
externalURL: "http://localhost",
expInitError: `could not find url property in settings`,
}, {
name: "Customized message",
settings: `{"url": "http://localhost", "message": "I'm a custom template and you have {{ len .Alerts.Firing }} firing alert."}`,
externalURL: "http://localhost",
alerts: []*types.Alert{
{
Alert: model.Alert{
@ -207,6 +206,7 @@ func TestGoogleChatNotifier(t *testing.T) {
}, {
name: "Missing field in template",
settings: `{"url": "http://localhost", "message": "I'm a custom template {{ .NotAField }} bad template"}`,
externalURL: "http://localhost",
alerts: []*types.Alert{
{
Alert: model.Alert{
@ -261,6 +261,7 @@ func TestGoogleChatNotifier(t *testing.T) {
}, {
name: "Invalid template",
settings: `{"url": "http://localhost", "message": "I'm a custom template {{ {.NotAField }} bad template"}`,
externalURL: "http://localhost",
alerts: []*types.Alert{
{
Alert: model.Alert{
@ -308,10 +309,104 @@ func TestGoogleChatNotifier(t *testing.T) {
},
expMsgError: nil,
},
{
name: "Empty external URL",
settings: `{ "url": "http://localhost" }`, // URL in settings = googlechat url
externalURL: "", // external URL = URL of grafana from configuration
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
},
},
},
expMsg: &outerStruct{
PreviewText: "[FIRING:1] (val1)",
FallbackText: "[FIRING:1] (val1)",
Cards: []card{
{
Header: header{
Title: "[FIRING:1] (val1)",
},
Sections: []section{
{
Widgets: []widget{
textParagraphWidget{
Text: text{
Text: "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\n",
},
},
// No button widget here since the external URL is not absolute
textParagraphWidget{
Text: text{
// RFC822 only has the minute, hence it works in most cases.
Text: "Grafana v" + setting.BuildVersion + " | " + constNow.Format(time.RFC822),
},
},
},
},
},
},
},
},
},
{
name: "Relative external URL",
settings: `{ "url": "http://localhost" }`, // URL in settings = googlechat url
externalURL: "/grafana", // external URL = URL of grafana from configuration
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
},
},
},
expMsg: &outerStruct{
PreviewText: "[FIRING:1] (val1)",
FallbackText: "[FIRING:1] (val1)",
Cards: []card{
{
Header: header{
Title: "[FIRING:1] (val1)",
},
Sections: []section{
{
Widgets: []widget{
textParagraphWidget{
Text: text{
Text: "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: /grafana/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: /grafana/d/abcd\nPanel: /grafana/d/abcd?viewPanel=efgh\n",
},
},
// No button widget here since the external URL is not absolute
textParagraphWidget{
Text: text{
// RFC822 only has the minute, hence it works in most cases.
Text: "Grafana v" + setting.BuildVersion + " | " + constNow.Format(time.RFC822),
},
},
},
},
},
},
},
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
tmpl := templateForTests(t)
externalURL, err := url.Parse(c.externalURL)
require.NoError(t, err)
tmpl.ExternalURL = externalURL
settingsJSON, err := simplejson.NewJson([]byte(c.settings))
require.NoError(t, err)

Loading…
Cancel
Save