diff --git a/pkg/alertmanager/alertmanagerserver/server.go b/pkg/alertmanager/alertmanagerserver/server.go index 2358393f6a..8cecbf04f2 100644 --- a/pkg/alertmanager/alertmanagerserver/server.go +++ b/pkg/alertmanager/alertmanagerserver/server.go @@ -218,7 +218,7 @@ func (server *Server) SetConfig(ctx context.Context, alertmanagerConfig *alertma config := alertmanagerConfig.AlertmanagerConfig() var err error - server.tmpl, err = template.FromGlobs(config.Templates) + server.tmpl, err = alertmanagertypes.FromGlobs(config.Templates) if err != nil { return err } diff --git a/pkg/query-service/contextlinks/links.go b/pkg/query-service/contextlinks/links.go index 9e48dfb1a2..3bb50e3a6b 100644 --- a/pkg/query-service/contextlinks/links.go +++ b/pkg/query-service/contextlinks/links.go @@ -148,7 +148,7 @@ func PrepareLinksToLogs(start, end time.Time, filterItems []v3.FilterItem) strin // i.e Severity text = WARN // If the Severity text is not part of the group by clause, then we add it as it is func PrepareFilters(labels map[string]string, whereClauseItems []v3.FilterItem, groupByItems []v3.AttributeKey, keys map[string]v3.AttributeKey) []v3.FilterItem { - var filterItems []v3.FilterItem + filterItems := make([]v3.FilterItem, 0) added := make(map[string]struct{}) diff --git a/pkg/types/alertmanagertypes/template.go b/pkg/types/alertmanagertypes/template.go new file mode 100644 index 0000000000..8f866e0eb4 --- /dev/null +++ b/pkg/types/alertmanagertypes/template.go @@ -0,0 +1,28 @@ +package alertmanagertypes + +import ( + "bytes" + "fmt" + + alertmanagertemplate "github.com/prometheus/alertmanager/template" +) + +// FromGlobs overrides the default alertmanager template to add a ruleIdPath template. +// This is used to generate a link to the rule in the alertmanager. +// +// It explicitly checks for a ruleId that is a number and then generates a path to the rule. +func FromGlobs(paths []string) (*alertmanagertemplate.Template, error) { + t, err := alertmanagertemplate.FromGlobs(paths) + if err != nil { + return nil, err + } + + if err := t.Parse(bytes.NewReader([]byte(` + {{ define "__ruleIdPath" }}{{ range .CommonLabels.SortedPairs }}{{ if eq .Name "ruleId" }}{{ if match "^[0-9]+$" .Value }}/edit?ruleId={{ .Value | urlquery }}{{ end }}{{ end }}{{ end }}{{ end }} + {{ define "__alertmanagerURL" }}{{ .ExternalURL }}/alerts{{ template "__ruleIdPath" . }}{{ end }} + `))); err != nil { + return nil, fmt.Errorf("error parsing alertmanager templates: %w", err) + } + + return t, nil +} diff --git a/pkg/types/alertmanagertypes/template_test.go b/pkg/types/alertmanagertypes/template_test.go new file mode 100644 index 0000000000..8d842c0f90 --- /dev/null +++ b/pkg/types/alertmanagertypes/template_test.go @@ -0,0 +1,141 @@ +package alertmanagertypes + +import ( + "net/url" + "testing" + "time" + + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFromGlobs(t *testing.T) { + template, err := FromGlobs([]string{}) + require.NoError(t, err) + template.ExternalURL = &url.URL{Scheme: "http", Host: "localhost:8080", Path: ""} + + testCases := []struct { + name string + alerts []*types.Alert + expected string + }{ + { + name: "SingleAlertWithValidRuleId", + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "ruleId": "439453587", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + }, + expected: "http://localhost:8080/alerts/edit?ruleId=439453587", + }, + { + name: "SingleAlertWithInvalidRuleId", + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "ruleId": "43textabc", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + }, + expected: "http://localhost:8080/alerts", + }, + { + name: "MultipleAlertsWithMismatchingRuleId", + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "ruleId": "1", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "ruleId": "2", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + }, + expected: "http://localhost:8080/alerts", + }, + { + name: "MultipleAlertsWithMatchingRuleId", + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "ruleId": "1", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "ruleId": "1", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + }, + expected: "http://localhost:8080/alerts/edit?ruleId=1", + }, + { + name: "MultipleAlertsWithNoRuleId", + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "label1": "1", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + { + Alert: model.Alert{ + Labels: model.LabelSet{ + "label2": "2", + }, + }, + UpdatedAt: time.Now(), + Timeout: false, + }, + }, + expected: "http://localhost:8080/alerts", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + data := template.Data("__receiver", model.LabelSet{}, tc.alerts...) + + url, err := template.ExecuteTextString(`{{ template "__alertmanagerURL" . }}`, data) + require.NoError(t, err) + assert.Equal(t, tc.expected, url) + + url, err = template.ExecuteHTMLString(`{{ template "__alertmanagerURL" . }}`, data) + require.NoError(t, err) + assert.Equal(t, tc.expected, url) + }) + } +}