diff --git a/go.mod b/go.mod index 0ea12b48..1186fa5a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/grafana/alerting go 1.18 require ( + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 github.com/go-kit/log v0.2.1 github.com/go-openapi/strfmt v0.21.3 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 982e965f..ee8f850b 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= diff --git a/receivers/pagerduty/config.go b/receivers/pagerduty/config.go index 21e921f4..90b04eb9 100644 --- a/receivers/pagerduty/config.go +++ b/receivers/pagerduty/config.go @@ -17,13 +17,24 @@ const ( DefaultClient = "Grafana" ) -func defaultCustomDetails() map[string]string { - return map[string]string{ - "firing": `{{ template "__text_alert_list" .Alerts.Firing }}`, - "resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, - "num_firing": `{{ .Alerts.Firing | len }}`, - "num_resolved": `{{ .Alerts.Resolved | len }}`, +var defaultDetails = map[string]string{ + "firing": `{{ template "__text_alert_list" .Alerts.Firing }}`, + "resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, + "num_firing": `{{ .Alerts.Firing | len }}`, + "num_resolved": `{{ .Alerts.Resolved | len }}`, +} + +// mergeDetails merges the default details with the user-defined ones. +// Default values get overwritten in case of duplicate keys. +func mergeDetails(userDefinedDetails map[string]string) map[string]string { + mergedDetails := make(map[string]string) + for k, v := range defaultDetails { + mergedDetails[k] = v + } + for k, v := range userDefinedDetails { + mergedDetails[k] = v } + return mergedDetails } var getHostname = func() (string, error) { @@ -31,16 +42,16 @@ var getHostname = func() (string, error) { } type Config struct { - Key string `json:"integrationKey,omitempty" yaml:"integrationKey,omitempty"` - Severity string `json:"severity,omitempty" yaml:"severity,omitempty"` - CustomDetails map[string]string `json:"-" yaml:"-"` // TODO support the settings in the config - Class string `json:"class,omitempty" yaml:"class,omitempty"` - Component string `json:"component,omitempty" yaml:"component,omitempty"` - Group string `json:"group,omitempty" yaml:"group,omitempty"` - Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` - Source string `json:"source,omitempty" yaml:"source,omitempty"` - Client string `json:"client,omitempty" yaml:"client,omitempty"` - ClientURL string `json:"client_url,omitempty" yaml:"client_url,omitempty"` + Key string `json:"integrationKey,omitempty" yaml:"integrationKey,omitempty"` + Severity string `json:"severity,omitempty" yaml:"severity,omitempty"` + Details map[string]string `json:"details,omitempty" yaml:"details,omitempty"` + Class string `json:"class,omitempty" yaml:"class,omitempty"` + Component string `json:"component,omitempty" yaml:"component,omitempty"` + Group string `json:"group,omitempty" yaml:"group,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Source string `json:"source,omitempty" yaml:"source,omitempty"` + Client string `json:"client,omitempty" yaml:"client,omitempty"` + ClientURL string `json:"client_url,omitempty" yaml:"client_url,omitempty"` } func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Config, error) { @@ -55,7 +66,7 @@ func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Confi return Config{}, errors.New("could not find integration key property in settings") } - settings.CustomDetails = defaultCustomDetails() + settings.Details = mergeDetails(settings.Details) if settings.Severity == "" { settings.Severity = DefaultSeverity diff --git a/receivers/pagerduty/config_test.go b/receivers/pagerduty/config_test.go index 3d252fa7..c79a02ac 100644 --- a/receivers/pagerduty/config_test.go +++ b/receivers/pagerduty/config_test.go @@ -44,16 +44,16 @@ func TestNewConfig(t *testing.T) { name: "Minimal valid configuration", settings: `{"integrationKey": "test-api-key" }`, expectedConfig: Config{ - Key: "test-api-key", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: hostName, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "test-api-key", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostName, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, }, { @@ -63,16 +63,16 @@ func TestNewConfig(t *testing.T) { "integrationKey": []byte("test-api-key"), }, expectedConfig: Config{ - Key: "test-api-key", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: hostName, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "test-api-key", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostName, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, }, { @@ -82,16 +82,16 @@ func TestNewConfig(t *testing.T) { "integrationKey": []byte("test-api-key"), }, expectedConfig: Config{ - Key: "test-api-key", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: hostName, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "test-api-key", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostName, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, }, { @@ -111,32 +111,32 @@ func TestNewConfig(t *testing.T) { "client_url": "" }`, expectedConfig: Config{ - Key: "test-api-key", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: hostName, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "test-api-key", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostName, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, }, { name: "Extract all fields", settings: FullValidConfigForTesting, expectedConfig: Config{ - Key: "test-api-key", - Severity: "test-severity", - CustomDetails: defaultCustomDetails(), - Class: "test-class", - Component: "test-component", - Group: "test-group", - Summary: "test-summary", - Source: "test-source", - Client: "test-client", - ClientURL: "test-client-url", + Key: "test-api-key", + Severity: "test-severity", + Details: defaultDetails, + Class: "test-class", + Component: "test-component", + Group: "test-group", + Summary: "test-summary", + Source: "test-source", + Client: "test-client", + ClientURL: "test-client-url", }, }, { @@ -144,42 +144,113 @@ func TestNewConfig(t *testing.T) { settings: FullValidConfigForTesting, secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), expectedConfig: Config{ - Key: "test-secret-api-key", - Severity: "test-severity", - CustomDetails: defaultCustomDetails(), - Class: "test-class", - Component: "test-component", - Group: "test-group", - Summary: "test-summary", - Source: "test-source", - Client: "test-client", - ClientURL: "test-client-url", + Key: "test-secret-api-key", + Severity: "test-severity", + Details: defaultDetails, + Class: "test-class", + Component: "test-component", + Group: "test-group", + Summary: "test-summary", + Source: "test-source", + Client: "test-client", + ClientURL: "test-client-url", }, }, { - name: "Should ignore custom details", + name: "Should merge default details with user-defined ones", secureSettings: map[string][]byte{ "integrationKey": []byte("test-api-key"), }, settings: `{ - "custom_details" : { - "test" : "test" + "details" : { + "test-field" : "test", + "test-field-2": "test-2" + } + }`, + expectedConfig: Config{ + Key: "test-api-key", + Severity: DefaultSeverity, + Details: map[string]string{ + "firing": `{{ template "__text_alert_list" .Alerts.Firing }}`, + "resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, + "num_firing": `{{ .Alerts.Firing | len }}`, + "num_resolved": `{{ .Alerts.Resolved | len }}`, + "test-field": "test", + "test-field-2": "test-2", + }, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostName, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", + }, + }, + { + name: "Should overwrite default details with user-defined ones when keys are duplicated", + secureSettings: map[string][]byte{ + "integrationKey": []byte("test-api-key"), + }, + settings: `{ + "details" : { + "firing" : "test", + "resolved": "maybe", + "num_firing": "a lot", + "num_resolved": "just a few" + } + }`, + expectedConfig: Config{ + Key: "test-api-key", + Severity: DefaultSeverity, + Details: map[string]string{ + "firing": "test", + "resolved": "maybe", + "num_firing": "a lot", + "num_resolved": "just a few", }, - "CustomDetails" : { - "test" : "test" + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostName, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", + }, + }, + { + name: "Custom details should be case-sensitive", + secureSettings: map[string][]byte{ + "integrationKey": []byte("test-api-key"), + }, + settings: `{ + "details" : { + "Firing" : "test", + "Resolved": "maybe", + "nuM_firing": "a lot", + "num_reSolved": "just a few" } }`, expectedConfig: Config{ - Key: "test-api-key", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: hostName, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "test-api-key", + Severity: DefaultSeverity, + Details: map[string]string{ + "firing": `{{ template "__text_alert_list" .Alerts.Firing }}`, + "resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, + "num_firing": `{{ .Alerts.Firing | len }}`, + "num_resolved": `{{ .Alerts.Resolved | len }}`, + "Firing": "test", + "Resolved": "maybe", + "nuM_firing": "a lot", + "num_reSolved": "just a few", + }, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostName, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, }, { @@ -194,16 +265,16 @@ func TestNewConfig(t *testing.T) { return "", errors.New("test") }, expectedConfig: Config{ - Key: "test-api-key", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: "test-client", - Client: "test-client", - ClientURL: "{{ .ExternalURL }}", + Key: "test-api-key", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: "test-client", + Client: "test-client", + ClientURL: "{{ .ExternalURL }}", }, }, } diff --git a/receivers/pagerduty/pagerduty.go b/receivers/pagerduty/pagerduty.go index e061dd92..5e9c770e 100644 --- a/receivers/pagerduty/pagerduty.go +++ b/receivers/pagerduty/pagerduty.go @@ -1,11 +1,14 @@ package pagerduty import ( + "bytes" "context" "encoding/json" "fmt" "strings" + "github.com/alecthomas/units" + "github.com/pkg/errors" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -20,6 +23,8 @@ import ( const ( // https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgx-send-an-alert-event - 1024 characters or runes. pagerDutyMaxV2SummaryLenRunes = 1024 + // https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview#size-limits - 512 KB. + pagerDutyMaxEventSize int = 512000 ) const ( @@ -74,15 +79,30 @@ func (pn *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error return false, fmt.Errorf("build pagerduty message: %w", err) } - body, err := json.Marshal(msg) - if err != nil { - return false, fmt.Errorf("marshal json: %w", err) + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(msg); err != nil { + return false, fmt.Errorf("failed to encode PagerDuty message: %w", err) + } + + // This payload size check is taken from the original implementation of the notifier in Alertmanager. + // https://github.com/prometheus/alertmanager/blob/41eb1213bb1c7ce0aa9e6464e297976d9c81cfe5/notify/pagerduty/pagerduty.go#L126-L142 + if buf.Len() > pagerDutyMaxEventSize { + bufSize := units.MetricBytes(buf.Len()).String() + maxEventSize := units.MetricBytes(pagerDutyMaxEventSize).String() + truncatedMsg := fmt.Sprintf("Custom details have been removed because the original event exceeds the maximum size of %s", maxEventSize) + msg.Payload.CustomDetails = map[string]string{"error": truncatedMsg} + pn.log.Warn("Truncated details", "maxSize", maxEventSize, "actualSize", bufSize) + + buf.Reset() + if err := json.NewEncoder(&buf).Encode(msg); err != nil { + return false, errors.Wrap(err, "failed to encode PagerDuty message") + } } pn.log.Info("notifying Pagerduty", "event_type", eventType) cmd := &receivers.SendWebhookSettings{ URL: APIURL, - Body: string(body), + Body: buf.String(), HTTPMethod: "POST", HTTPHeader: map[string]string{ "Content-Type": "application/json", @@ -109,8 +129,8 @@ func (pn *Notifier) buildPagerdutyMessage(ctx context.Context, alerts model.Aler var tmplErr error tmpl, data := template2.TmplText(ctx, pn.tmpl, as, pn.log, &tmplErr) - details := make(map[string]string, len(pn.settings.CustomDetails)) - for k, v := range pn.settings.CustomDetails { + details := make(map[string]string, len(pn.settings.Details)) + for k, v := range pn.settings.Details { detail, err := pn.tmpl.ExecuteTextString(v, data) if err != nil { return nil, "", fmt.Errorf("%q: failed to template %q: %w", k, v, err) diff --git a/receivers/pagerduty/pagerduty_test.go b/receivers/pagerduty/pagerduty_test.go index 92953fa3..9e6e4e24 100644 --- a/receivers/pagerduty/pagerduty_test.go +++ b/receivers/pagerduty/pagerduty_test.go @@ -40,16 +40,16 @@ func TestNotify(t *testing.T) { { name: "Default config with one alert", settings: Config{ - Key: "abcdefgh0123456789", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: hostname, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "abcdefgh0123456789", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostname, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, alerts: []*types.Alert{ { @@ -86,16 +86,16 @@ func TestNotify(t *testing.T) { { name: "should map unknown severity", settings: Config{ - Key: "abcdefgh0123456789", - Severity: "{{ .CommonLabels.severity }}", - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: templates.DefaultMessageTitleEmbed, - Source: hostname, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "abcdefgh0123456789", + Severity: "{{ .CommonLabels.severity }}", + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostname, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, alerts: []*types.Alert{ { @@ -132,16 +132,16 @@ func TestNotify(t *testing.T) { { name: "Should expand templates in fields", settings: Config{ - Key: "abcdefgh0123456789", - Severity: "{{ .CommonLabels.severity }}", - CustomDetails: defaultCustomDetails(), - Class: "{{ .CommonLabels.class }}", - Component: "{{ .CommonLabels.component }}", - Group: "{{ .CommonLabels.group }}", - Summary: templates.DefaultMessageTitleEmbed, - Source: "{{ .CommonLabels.source }}", - Client: "client-{{ .CommonLabels.source }}", - ClientURL: "http://localhost:20200/{{ .CommonLabels.group }}", + Key: "abcdefgh0123456789", + Severity: "{{ .CommonLabels.severity }}", + Details: defaultDetails, + Class: "{{ .CommonLabels.class }}", + Component: "{{ .CommonLabels.component }}", + Group: "{{ .CommonLabels.group }}", + Summary: templates.DefaultMessageTitleEmbed, + Source: "{{ .CommonLabels.source }}", + Client: "client-{{ .CommonLabels.source }}", + ClientURL: "http://localhost:20200/{{ .CommonLabels.group }}", }, alerts: []*types.Alert{ { @@ -175,19 +175,125 @@ func TestNotify(t *testing.T) { }, expMsgError: nil, }, + { + name: "Should expand custom details", + settings: Config{ + Key: "abcdefgh0123456789", + Severity: "{{ .CommonLabels.severity }}", + Details: map[string]string{ + "firing": `{{ template "__text_alert_list" .Alerts.Firing }}`, + "resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, + "num_firing": `{{ .Alerts.Firing | len }}`, + "num_resolved": `{{ .Alerts.Resolved | len }}`, + "test-field": "{{ len .Alerts }}", + "test-field-2": "{{ len \"abcde\"}}", + }, + Class: "{{ .CommonLabels.class }}", + Component: "{{ .CommonLabels.component }}", + Group: "{{ .CommonLabels.group }}", + Summary: templates.DefaultMessageTitleEmbed, + Source: "{{ .CommonLabels.source }}", + Client: "client-{{ .CommonLabels.source }}", + ClientURL: "http://localhost:20200/{{ .CommonLabels.group }}", + }, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1", "severity": "critical", "class": "test-class", "group": "test-group", "component": "test-component", "source": "test-source"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"}, + }, + }, + }, + expMsg: &pagerDutyMessage{ + RoutingKey: "abcdefgh0123456789", + DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733", + EventAction: "trigger", + Payload: pagerDutyPayload{ + Summary: "[FIRING:1] (test-class test-component test-group val1 critical test-source)", + Source: "test-source", + Severity: "critical", + Class: "test-class", + Component: "test-component", + Group: "test-group", + CustomDetails: map[string]string{ + "firing": "\nValue: [no value]\nLabels:\n - alertname = alert1\n - class = test-class\n - component = test-component\n - group = test-group\n - lbl1 = val1\n - severity = critical\n - source = test-source\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=class%3Dtest-class&matcher=component%3Dtest-component&matcher=group%3Dtest-group&matcher=lbl1%3Dval1&matcher=severity%3Dcritical&matcher=source%3Dtest-source\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n", + "num_firing": "1", + "num_resolved": "0", + "resolved": "", + "test-field": "1", + "test-field-2": "5", + }, + }, + Client: "client-test-source", + ClientURL: "http://localhost:20200/test-group", + Links: []pagerDutyLink{{HRef: "http://localhost", Text: "External URL"}}, + }, + expMsgError: nil, + }, + { + name: "Should overwrite default custom details with user-defined ones when keys are duplicated", + settings: Config{ + Key: "abcdefgh0123456789", + Severity: "{{ .CommonLabels.severity }}", + Details: map[string]string{ + "firing": `{{ len "abcde" }}`, + "resolved": "test value", + "num_firing": "{{ .Alerts.Firing | len | eq 100 }}", + "num_resolved": "just another test value", + }, + Class: "{{ .CommonLabels.class }}", + Component: "{{ .CommonLabels.component }}", + Group: "{{ .CommonLabels.group }}", + Summary: templates.DefaultMessageTitleEmbed, + Source: "{{ .CommonLabels.source }}", + Client: "client-{{ .CommonLabels.source }}", + ClientURL: "http://localhost:20200/{{ .CommonLabels.group }}", + }, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1", "severity": "critical", "class": "test-class", "group": "test-group", "component": "test-component", "source": "test-source"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"}, + }, + }, + }, + expMsg: &pagerDutyMessage{ + RoutingKey: "abcdefgh0123456789", + DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733", + EventAction: "trigger", + Payload: pagerDutyPayload{ + Summary: "[FIRING:1] (test-class test-component test-group val1 critical test-source)", + Source: "test-source", + Severity: "critical", + Class: "test-class", + Component: "test-component", + Group: "test-group", + CustomDetails: map[string]string{ + "firing": "5", + "resolved": "test value", + "num_firing": "false", + "num_resolved": "just another test value", + }, + }, + Client: "client-test-source", + ClientURL: "http://localhost:20200/test-group", + Links: []pagerDutyLink{{HRef: "http://localhost", Text: "External URL"}}, + }, + expMsgError: nil, + }, { name: "Default config with one alert and custom summary", settings: Config{ - Key: "abcdefgh0123456789", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: "Alerts firing: {{ len .Alerts.Firing }}", - Source: hostname, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "abcdefgh0123456789", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: "Alerts firing: {{ len .Alerts.Firing }}", + Source: hostname, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, alerts: []*types.Alert{ { @@ -223,16 +329,16 @@ func TestNotify(t *testing.T) { }, { name: "Custom config with multiple alerts", settings: Config{ - Key: "abcdefgh0123456789", - Severity: "warning", - CustomDetails: defaultCustomDetails(), - Class: "{{ .Status }}", - Component: "My Grafana", - Group: "my_group", - Summary: templates.DefaultMessageTitleEmbed, - Source: hostname, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "abcdefgh0123456789", + Severity: "warning", + Details: defaultDetails, + Class: "{{ .Status }}", + Component: "My Grafana", + Group: "my_group", + Summary: templates.DefaultMessageTitleEmbed, + Source: hostname, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, alerts: []*types.Alert{ { @@ -274,16 +380,16 @@ func TestNotify(t *testing.T) { { name: "should truncate long summary", settings: Config{ - Key: "abcdefgh0123456789", - Severity: DefaultSeverity, - CustomDetails: defaultCustomDetails(), - Class: DefaultClass, - Component: "Grafana", - Group: DefaultGroup, - Summary: strings.Repeat("1", rand.Intn(100)+1025), - Source: hostname, - Client: DefaultClient, - ClientURL: "{{ .ExternalURL }}", + Key: "abcdefgh0123456789", + Severity: DefaultSeverity, + Details: defaultDetails, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: strings.Repeat("1", rand.Intn(100)+1025), + Source: hostname, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", }, alerts: []*types.Alert{ { @@ -317,6 +423,51 @@ func TestNotify(t *testing.T) { }, expMsgError: nil, }, + { + name: "Should remove custom details if the payload is too large", + settings: Config{ + Key: "abcdefgh0123456789", + Severity: DefaultSeverity, + Details: map[string]string{ + "long": strings.Repeat("a", pagerDutyMaxEventSize+1), + }, + Class: DefaultClass, + Component: "Grafana", + Group: DefaultGroup, + Summary: templates.DefaultMessageTitleEmbed, + Source: hostname, + Client: DefaultClient, + ClientURL: "{{ .ExternalURL }}", + }, + alerts: []*types.Alert{ + { + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"}, + }, + }, + }, + expMsg: &pagerDutyMessage{ + RoutingKey: "abcdefgh0123456789", + DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733", + EventAction: "trigger", + Payload: pagerDutyPayload{ + Summary: "[FIRING:1] (val1)", + Source: hostname, + Severity: DefaultSeverity, + Class: "default", + Component: "Grafana", + Group: "default", + CustomDetails: map[string]string{ + "error": "Custom details have been removed because the original event exceeds the maximum size of 512KB", + }, + }, + Client: "Grafana", + ClientURL: "http://localhost", + Links: []pagerDutyLink{{HRef: "http://localhost", Text: "External URL"}}, + }, + expMsgError: nil, + }, } for _, c := range cases {