diff --git a/template/template.go b/template/template.go index 961ad746..2f4e1ee3 100644 --- a/template/template.go +++ b/template/template.go @@ -15,16 +15,15 @@ package template import ( "bytes" + tmplhtml "html/template" "io/ioutil" "net/url" "path/filepath" "regexp" "sort" "strings" - "time" - - tmplhtml "html/template" tmpltext "text/template" + "time" "github.com/prometheus/common/model" @@ -315,6 +314,9 @@ func (t *Template) Data(recv string, groupLabels model.LabelSet, alerts ...*type commonAnnotations = alerts[0].Annotations.Clone() ) for _, a := range alerts[1:] { + if len(commonLabels) == 0 && len(commonAnnotations) == 0 { + break + } for ln, lv := range commonLabels { if a.Labels[ln] != lv { delete(commonLabels, ln) diff --git a/template/template_test.go b/template/template_test.go index 152e1bf6..7b503040 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -14,10 +14,14 @@ package template import ( + "net/url" "testing" + "time" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" + + "github.com/prometheus/alertmanager/types" ) func TestPairNames(t *testing.T) { @@ -118,3 +122,251 @@ func TestAlertsResolved(t *testing.T) { } } } + +func TestData(t *testing.T) { + u, err := url.Parse("http://example.com/") + require.NoError(t, err) + tmpl := &Template{ExternalURL: u} + startTime := time.Time{}.Add(1 * time.Second) + endTime := time.Time{}.Add(2 * time.Second) + + for _, tc := range []struct { + receiver string + groupLabels model.LabelSet + alerts []*types.Alert + + exp *Data + }{ + { + receiver: "webhook", + exp: &Data{ + Receiver: "webhook", + Status: "resolved", + Alerts: Alerts{}, + GroupLabels: KV{}, + CommonLabels: KV{}, + CommonAnnotations: KV{}, + ExternalURL: u.String(), + }, + }, + { + receiver: "webhook", + groupLabels: model.LabelSet{ + model.LabelName("job"): model.LabelValue("foo"), + }, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + StartsAt: startTime, + Labels: model.LabelSet{ + model.LabelName("severity"): model.LabelValue("warning"), + model.LabelName("job"): model.LabelValue("foo"), + }, + Annotations: model.LabelSet{ + model.LabelName("description"): model.LabelValue("something happened"), + model.LabelName("runbook"): model.LabelValue("foo"), + }, + }, + }, + { + Alert: model.Alert{ + StartsAt: startTime, + EndsAt: endTime, + Labels: model.LabelSet{ + model.LabelName("severity"): model.LabelValue("critical"), + model.LabelName("job"): model.LabelValue("foo"), + }, + Annotations: model.LabelSet{ + model.LabelName("description"): model.LabelValue("something else happened"), + model.LabelName("runbook"): model.LabelValue("foo"), + }, + }, + }, + }, + exp: &Data{ + Receiver: "webhook", + Status: "firing", + Alerts: Alerts{ + { + Status: "firing", + Labels: KV{"severity": "warning", "job": "foo"}, + Annotations: KV{"description": "something happened", "runbook": "foo"}, + StartsAt: startTime, + }, + { + Status: "resolved", + Labels: KV{"severity": "critical", "job": "foo"}, + Annotations: KV{"description": "something else happened", "runbook": "foo"}, + StartsAt: startTime, + EndsAt: endTime, + }, + }, + GroupLabels: KV{"job": "foo"}, + CommonLabels: KV{"job": "foo"}, + CommonAnnotations: KV{"runbook": "foo"}, + ExternalURL: u.String(), + }, + }, + { + receiver: "webhook", + groupLabels: model.LabelSet{}, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + StartsAt: startTime, + Labels: model.LabelSet{ + model.LabelName("severity"): model.LabelValue("warning"), + model.LabelName("job"): model.LabelValue("foo"), + }, + Annotations: model.LabelSet{ + model.LabelName("description"): model.LabelValue("something happened"), + model.LabelName("runbook"): model.LabelValue("foo"), + }, + }, + }, + { + Alert: model.Alert{ + StartsAt: startTime, + EndsAt: endTime, + Labels: model.LabelSet{ + model.LabelName("severity"): model.LabelValue("critical"), + model.LabelName("job"): model.LabelValue("bar"), + }, + Annotations: model.LabelSet{ + model.LabelName("description"): model.LabelValue("something else happened"), + model.LabelName("runbook"): model.LabelValue("bar"), + }, + }, + }, + }, + exp: &Data{ + Receiver: "webhook", + Status: "firing", + Alerts: Alerts{ + { + Status: "firing", + Labels: KV{"severity": "warning", "job": "foo"}, + Annotations: KV{"description": "something happened", "runbook": "foo"}, + StartsAt: startTime, + }, + { + Status: "resolved", + Labels: KV{"severity": "critical", "job": "bar"}, + Annotations: KV{"description": "something else happened", "runbook": "bar"}, + StartsAt: startTime, + EndsAt: endTime, + }, + }, + GroupLabels: KV{}, + CommonLabels: KV{}, + CommonAnnotations: KV{}, + ExternalURL: u.String(), + }, + }, + } { + tc := tc + t.Run("", func(t *testing.T) { + got := tmpl.Data(tc.receiver, tc.groupLabels, tc.alerts...) + require.Equal(t, tc.exp, got) + }) + } +} + +func TestTemplateExpansion(t *testing.T) { + tmpl, err := FromGlobs() + require.NoError(t, err) + + for _, tc := range []struct { + title string + in string + data interface{} + html bool + + exp string + fail bool + }{ + { + title: "Template without action", + in: `abc`, + exp: "abc", + }, + { + title: "Template with simple action", + in: `{{ "abc" }}`, + exp: "abc", + }, + { + title: "Template with invalid syntax", + in: `{{ `, + fail: true, + }, + { + title: "Template using toUpper", + in: `{{ "abc" | toUpper }}`, + exp: "ABC", + }, + { + title: "Template using toLower", + in: `{{ "ABC" | toLower }}`, + exp: "abc", + }, + { + title: "Template using title", + in: `{{ "abc" | title }}`, + exp: "Abc", + }, + { + title: "Template using positive match", + in: `{{ if match "^a" "abc"}}abc{{ end }}`, + exp: "abc", + }, + { + title: "Template using negative match", + in: `{{ if match "abcd" "abc" }}abc{{ end }}`, + exp: "", + }, + { + title: "Template using join", + in: `{{ . | join "," }}`, + data: []string{"a", "b", "c"}, + exp: "a,b,c", + }, + { + title: "Text template without HTML escaping", + in: `{{ "" }}`, + exp: "", + }, + { + title: "HTML template with escaping", + in: `{{ "" }}`, + html: true, + exp: "<b>", + }, + { + title: "HTML template using safeHTML", + in: `{{ "" | safeHtml }}`, + html: true, + exp: "", + }, + { + title: "Template using reReplaceAll", + in: `{{ reReplaceAll "ab" "AB" "abcdabcda"}}`, + exp: "ABcdABcda", + }, + } { + tc := tc + t.Run(tc.title, func(t *testing.T) { + f := tmpl.ExecuteTextString + if tc.html { + f = tmpl.ExecuteHTMLString + } + got, err := f(tc.in, tc.data) + if tc.fail { + require.NotNil(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.exp, got) + }) + } +}