Skip to content

Commit e838ea9

Browse files
committed
fix: pageduty custom_details in payload
This change fixes the `custom_details` value in PagerDuty V2 API payloads. Fixes #2477 Fixes #3218 Alertmanager PagerDuty configuration allows users to define `details` under `pagerduty_configs`: ```yaml [ details: { <string>: <tmpl_string>, ... } | default = { firing: '{{ template "pagerduty.default.instances" .Alerts.Firing }}' resolved: '{{ template "pagerduty.default.instances" .Alerts.Resolved }}' num_firing: '{{ .Alerts.Firing | len }}' num_resolved: '{{ .Alerts.Resolved | len }}' } ] ``` The internal Alertmanager configuration structure is defined as: ```go // PagerdutyConfig configures notifications via PagerDuty. type PagerdutyConfig struct { ... Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"` } ``` And the PagerDuty payload is defined as: ```go type pagerDutyPayload struct { ... CustomDetails map[string]string `json:"custom_details,omitempty"` ``` The current flow of configuration parsing and encoding is: 1. `pagerduty_configs[].details` are read from configuration as `map[string]string` 2. The `details` map values are parsed as text templates 3. The result is passed as `custom_details`, part of the JSON payload sent to PagerDuty API V2 Here is an example payload using `.Alerts.Firing`, which returns a list of currently firing alert objects in this group. Documented here: https://prometheus.io/docs/alerting/latest/notifications/#data This results in a payload like below: ```json { "client": "Alertmanager", ... "custom_details": { "firing": "Labels: - alertname = Server_Down ... Annotations: - summary = Server is down ... " } } ``` Using `map[string]string` in payloads cause the rendered YAML templates to be encoded as JSON strings. This is undesirable in most cases as the end-user might expect a nested JSON object which is machine readable/parsable by it's fields. This change: - adds logic to unmarshall `details` values as YAML with fallback to string - uses `map[string]interface{}` to encode `custom_details` to JSON - requires gopkg.in/yaml.v3, see go-yaml/yaml#591 - does not change existing yaml.v2 dependency, see #3322 Signed-off-by: Siavash Safi <[email protected]>
1 parent 0c1a85c commit e838ea9

File tree

3 files changed

+31
-15
lines changed

3 files changed

+31
-15
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ require (
4848
golang.org/x/tools v0.22.0
4949
gopkg.in/telebot.v3 v3.3.6
5050
gopkg.in/yaml.v2 v2.4.0
51+
gopkg.in/yaml.v3 v3.0.1
5152
)
5253

5354
require (
@@ -100,5 +101,4 @@ require (
100101
golang.org/x/sync v0.7.0 // indirect
101102
golang.org/x/sys v0.21.0 // indirect
102103
google.golang.org/protobuf v1.34.2 // indirect
103-
gopkg.in/yaml.v3 v3.0.1 // indirect
104104
)

notify/pagerduty/pagerduty.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/go-kit/log/level"
3030
commoncfg "github.com/prometheus/common/config"
3131
"github.com/prometheus/common/model"
32+
"gopkg.in/yaml.v3"
3233

3334
"github.com/prometheus/alertmanager/config"
3435
"github.com/prometheus/alertmanager/notify"
@@ -107,14 +108,14 @@ type pagerDutyImage struct {
107108
}
108109

109110
type pagerDutyPayload struct {
110-
Summary string `json:"summary"`
111-
Source string `json:"source"`
112-
Severity string `json:"severity"`
113-
Timestamp string `json:"timestamp,omitempty"`
114-
Class string `json:"class,omitempty"`
115-
Component string `json:"component,omitempty"`
116-
Group string `json:"group,omitempty"`
117-
CustomDetails map[string]string `json:"custom_details,omitempty"`
111+
Summary string `json:"summary"`
112+
Source string `json:"source"`
113+
Severity string `json:"severity"`
114+
Timestamp string `json:"timestamp,omitempty"`
115+
Class string `json:"class,omitempty"`
116+
Component string `json:"component,omitempty"`
117+
Group string `json:"group,omitempty"`
118+
CustomDetails map[string]interface{} `json:"custom_details,omitempty"`
118119
}
119120

120121
func (n *Notifier) encodeMessage(msg *pagerDutyMessage) (bytes.Buffer, error) {
@@ -129,7 +130,7 @@ func (n *Notifier) encodeMessage(msg *pagerDutyMessage) (bytes.Buffer, error) {
129130
if n.apiV1 != "" {
130131
msg.Details = map[string]string{"error": truncatedMsg}
131132
} else {
132-
msg.Payload.CustomDetails = map[string]string{"error": truncatedMsg}
133+
msg.Payload.CustomDetails = map[string]interface{}{"error": truncatedMsg}
133134
}
134135

135136
warningMsg := fmt.Sprintf("Truncated Details because message of size %s exceeds limit %s", units.MetricBytes(buf.Len()).String(), units.MetricBytes(maxEventSize).String())
@@ -210,7 +211,7 @@ func (n *Notifier) notifyV2(
210211
eventType string,
211212
key notify.Key,
212213
data *template.Data,
213-
details map[string]string,
214+
details map[string]interface{},
214215
as ...*types.Alert,
215216
) (bool, error) {
216217
var tmplErr error
@@ -333,7 +334,19 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
333334
if n.apiV1 != "" {
334335
return n.notifyV1(ctx, eventType, key, data, details, as...)
335336
}
336-
return n.notifyV2(ctx, eventType, key, data, details, as...)
337+
338+
customDetails := make(map[string]interface{}, len(n.conf.Details))
339+
for k, v := range details {
340+
var v2 interface{}
341+
// Try to unmarshall any rendered templates as YAML.
342+
if err := yaml.Unmarshal([]byte(v), &v2); err != nil {
343+
customDetails[k] = v
344+
continue
345+
}
346+
customDetails[k] = v2
347+
}
348+
349+
return n.notifyV2(ctx, eventType, key, data, customDetails, as...)
337350
}
338351

339352
func errDetails(status int, body io.Reader) string {

notify/pagerduty/pagerduty_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,18 @@ func TestErrDetails(t *testing.T) {
323323
}
324324

325325
func TestEventSizeEnforcement(t *testing.T) {
326-
bigDetails := map[string]string{
326+
bigDetailsV1 := map[string]string{
327+
"firing": strings.Repeat("a", 513000),
328+
}
329+
bigDetailsV2 := map[string]interface{}{
327330
"firing": strings.Repeat("a", 513000),
328331
}
329332

330333
// V1 Messages
331334
msgV1 := &pagerDutyMessage{
332335
ServiceKey: "01234567890123456789012345678901",
333336
EventType: "trigger",
334-
Details: bigDetails,
337+
Details: bigDetailsV1,
335338
}
336339

337340
notifierV1, err := New(
@@ -353,7 +356,7 @@ func TestEventSizeEnforcement(t *testing.T) {
353356
RoutingKey: "01234567890123456789012345678901",
354357
EventAction: "trigger",
355358
Payload: &pagerDutyPayload{
356-
CustomDetails: bigDetails,
359+
CustomDetails: bigDetailsV2,
357360
},
358361
}
359362

0 commit comments

Comments
 (0)