1
2
3
4
5
6
7
8
9
10
11
12
13
14 package pagerduty
15
16 import (
17 "bytes"
18 "context"
19 "encoding/json"
20 "fmt"
21 "io"
22 "net/http"
23 "net/http/httptest"
24 "net/url"
25 "os"
26 "strings"
27 "testing"
28 "time"
29
30 "github.com/go-kit/log"
31 commoncfg "github.com/prometheus/common/config"
32 "github.com/prometheus/common/model"
33 "github.com/stretchr/testify/require"
34
35 "github.com/prometheus/alertmanager/config"
36 "github.com/prometheus/alertmanager/notify"
37 "github.com/prometheus/alertmanager/notify/test"
38 "github.com/prometheus/alertmanager/types"
39 )
40
41 func TestPagerDutyRetryV1(t *testing.T) {
42 notifier, err := New(
43 &config.PagerdutyConfig{
44 ServiceKey: config.Secret("01234567890123456789012345678901"),
45 HTTPConfig: &commoncfg.HTTPClientConfig{},
46 },
47 test.CreateTmpl(t),
48 log.NewNopLogger(),
49 )
50 require.NoError(t, err)
51
52 retryCodes := append(test.DefaultRetryCodes(), http.StatusForbidden)
53 for statusCode, expected := range test.RetryTests(retryCodes) {
54 actual, _ := notifier.retrier.Check(statusCode, nil)
55 require.Equal(t, expected, actual, fmt.Sprintf("retryv1 - error on status %d", statusCode))
56 }
57 }
58
59 func TestPagerDutyRetryV2(t *testing.T) {
60 notifier, err := New(
61 &config.PagerdutyConfig{
62 RoutingKey: config.Secret("01234567890123456789012345678901"),
63 HTTPConfig: &commoncfg.HTTPClientConfig{},
64 },
65 test.CreateTmpl(t),
66 log.NewNopLogger(),
67 )
68 require.NoError(t, err)
69
70 retryCodes := append(test.DefaultRetryCodes(), http.StatusTooManyRequests)
71 for statusCode, expected := range test.RetryTests(retryCodes) {
72 actual, _ := notifier.retrier.Check(statusCode, nil)
73 require.Equal(t, expected, actual, fmt.Sprintf("retryv2 - error on status %d", statusCode))
74 }
75 }
76
77 func TestPagerDutyRedactedURLV1(t *testing.T) {
78 ctx, u, fn := test.GetContextWithCancelingURL()
79 defer fn()
80
81 key := "01234567890123456789012345678901"
82 notifier, err := New(
83 &config.PagerdutyConfig{
84 ServiceKey: config.Secret(key),
85 HTTPConfig: &commoncfg.HTTPClientConfig{},
86 },
87 test.CreateTmpl(t),
88 log.NewNopLogger(),
89 )
90 require.NoError(t, err)
91 notifier.apiV1 = u.String()
92
93 test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
94 }
95
96 func TestPagerDutyRedactedURLV2(t *testing.T) {
97 ctx, u, fn := test.GetContextWithCancelingURL()
98 defer fn()
99
100 key := "01234567890123456789012345678901"
101 notifier, err := New(
102 &config.PagerdutyConfig{
103 URL: &config.URL{URL: u},
104 RoutingKey: config.Secret(key),
105 HTTPConfig: &commoncfg.HTTPClientConfig{},
106 },
107 test.CreateTmpl(t),
108 log.NewNopLogger(),
109 )
110 require.NoError(t, err)
111
112 test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
113 }
114
115 func TestPagerDutyV1ServiceKeyFromFile(t *testing.T) {
116 key := "01234567890123456789012345678901"
117 f, err := os.CreateTemp("", "pagerduty_test")
118 require.NoError(t, err, "creating temp file failed")
119 _, err = f.WriteString(key)
120 require.NoError(t, err, "writing to temp file failed")
121
122 ctx, u, fn := test.GetContextWithCancelingURL()
123 defer fn()
124
125 notifier, err := New(
126 &config.PagerdutyConfig{
127 ServiceKeyFile: f.Name(),
128 HTTPConfig: &commoncfg.HTTPClientConfig{},
129 },
130 test.CreateTmpl(t),
131 log.NewNopLogger(),
132 )
133 require.NoError(t, err)
134 notifier.apiV1 = u.String()
135
136 test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
137 }
138
139 func TestPagerDutyV2RoutingKeyFromFile(t *testing.T) {
140 key := "01234567890123456789012345678901"
141 f, err := os.CreateTemp("", "pagerduty_test")
142 require.NoError(t, err, "creating temp file failed")
143 _, err = f.WriteString(key)
144 require.NoError(t, err, "writing to temp file failed")
145
146 ctx, u, fn := test.GetContextWithCancelingURL()
147 defer fn()
148
149 notifier, err := New(
150 &config.PagerdutyConfig{
151 URL: &config.URL{URL: u},
152 RoutingKeyFile: f.Name(),
153 HTTPConfig: &commoncfg.HTTPClientConfig{},
154 },
155 test.CreateTmpl(t),
156 log.NewNopLogger(),
157 )
158 require.NoError(t, err)
159
160 test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
161 }
162
163 func TestPagerDutyTemplating(t *testing.T) {
164 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
165 dec := json.NewDecoder(r.Body)
166 out := make(map[string]interface{})
167 err := dec.Decode(&out)
168 if err != nil {
169 panic(err)
170 }
171 }))
172 defer srv.Close()
173 u, _ := url.Parse(srv.URL)
174
175 for _, tc := range []struct {
176 title string
177 cfg *config.PagerdutyConfig
178
179 retry bool
180 errMsg string
181 }{
182 {
183 title: "full-blown message",
184 cfg: &config.PagerdutyConfig{
185 RoutingKey: config.Secret("01234567890123456789012345678901"),
186 Images: []config.PagerdutyImage{
187 {
188 Src: "{{ .Status }}",
189 Alt: "{{ .Status }}",
190 Href: "{{ .Status }}",
191 },
192 },
193 Links: []config.PagerdutyLink{
194 {
195 Href: "{{ .Status }}",
196 Text: "{{ .Status }}",
197 },
198 },
199 Details: map[string]string{
200 "firing": `{{ template "pagerduty.default.instances" .Alerts.Firing }}`,
201 "resolved": `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`,
202 "num_firing": `{{ .Alerts.Firing | len }}`,
203 "num_resolved": `{{ .Alerts.Resolved | len }}`,
204 },
205 },
206 },
207 {
208 title: "details with templating errors",
209 cfg: &config.PagerdutyConfig{
210 RoutingKey: config.Secret("01234567890123456789012345678901"),
211 Details: map[string]string{
212 "firing": `{{ template "pagerduty.default.instances" .Alerts.Firing`,
213 "resolved": `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`,
214 "num_firing": `{{ .Alerts.Firing | len }}`,
215 "num_resolved": `{{ .Alerts.Resolved | len }}`,
216 },
217 },
218 errMsg: "failed to template",
219 },
220 {
221 title: "v2 message with templating errors",
222 cfg: &config.PagerdutyConfig{
223 RoutingKey: config.Secret("01234567890123456789012345678901"),
224 Severity: "{{ ",
225 },
226 errMsg: "failed to template",
227 },
228 {
229 title: "v1 message with templating errors",
230 cfg: &config.PagerdutyConfig{
231 ServiceKey: config.Secret("01234567890123456789012345678901"),
232 Client: "{{ ",
233 },
234 errMsg: "failed to template",
235 },
236 {
237 title: "routing key cannot be empty",
238 cfg: &config.PagerdutyConfig{
239 RoutingKey: config.Secret(`{{ "" }}`),
240 },
241 errMsg: "routing key cannot be empty",
242 },
243 {
244 title: "service_key cannot be empty",
245 cfg: &config.PagerdutyConfig{
246 ServiceKey: config.Secret(`{{ "" }}`),
247 },
248 errMsg: "service key cannot be empty",
249 },
250 } {
251 t.Run(tc.title, func(t *testing.T) {
252 tc.cfg.URL = &config.URL{URL: u}
253 tc.cfg.HTTPConfig = &commoncfg.HTTPClientConfig{}
254 pd, err := New(tc.cfg, test.CreateTmpl(t), log.NewNopLogger())
255 require.NoError(t, err)
256 if pd.apiV1 != "" {
257 pd.apiV1 = u.String()
258 }
259
260 ctx := context.Background()
261 ctx = notify.WithGroupKey(ctx, "1")
262
263 ok, err := pd.Notify(ctx, []*types.Alert{
264 {
265 Alert: model.Alert{
266 Labels: model.LabelSet{
267 "lbl1": "val1",
268 },
269 StartsAt: time.Now(),
270 EndsAt: time.Now().Add(time.Hour),
271 },
272 },
273 }...)
274 if tc.errMsg == "" {
275 require.NoError(t, err)
276 } else {
277 require.Error(t, err)
278 require.Contains(t, err.Error(), tc.errMsg)
279 }
280 require.Equal(t, tc.retry, ok)
281 })
282 }
283 }
284
285 func TestErrDetails(t *testing.T) {
286 for _, tc := range []struct {
287 status int
288 body io.Reader
289
290 exp string
291 }{
292 {
293 status: http.StatusBadRequest,
294 body: bytes.NewBuffer([]byte(
295 `{"status":"invalid event","message":"Event object is invalid","errors":["Length of 'routing_key' is incorrect (should be 32 characters)"]}`,
296 )),
297
298 exp: "Length of 'routing_key' is incorrect",
299 },
300 {
301 status: http.StatusBadRequest,
302 body: bytes.NewBuffer([]byte(`{"status"}`)),
303
304 exp: "",
305 },
306 {
307 status: http.StatusBadRequest,
308
309 exp: "",
310 },
311 {
312 status: http.StatusTooManyRequests,
313
314 exp: "",
315 },
316 } {
317 tc := tc
318 t.Run("", func(t *testing.T) {
319 err := errDetails(tc.status, tc.body)
320 require.Contains(t, err, tc.exp)
321 })
322 }
323 }
324
325 func TestEventSizeEnforcement(t *testing.T) {
326 bigDetails := map[string]string{
327 "firing": strings.Repeat("a", 513000),
328 }
329
330
331 msgV1 := &pagerDutyMessage{
332 ServiceKey: "01234567890123456789012345678901",
333 EventType: "trigger",
334 Details: bigDetails,
335 }
336
337 notifierV1, err := New(
338 &config.PagerdutyConfig{
339 ServiceKey: config.Secret("01234567890123456789012345678901"),
340 HTTPConfig: &commoncfg.HTTPClientConfig{},
341 },
342 test.CreateTmpl(t),
343 log.NewNopLogger(),
344 )
345 require.NoError(t, err)
346
347 encodedV1, err := notifierV1.encodeMessage(msgV1)
348 require.NoError(t, err)
349 require.Contains(t, encodedV1.String(), `"details":{"error":"Custom details have been removed because the original event exceeds the maximum size of 512KB"}`)
350
351
352 msgV2 := &pagerDutyMessage{
353 RoutingKey: "01234567890123456789012345678901",
354 EventAction: "trigger",
355 Payload: &pagerDutyPayload{
356 CustomDetails: bigDetails,
357 },
358 }
359
360 notifierV2, err := New(
361 &config.PagerdutyConfig{
362 RoutingKey: config.Secret("01234567890123456789012345678901"),
363 HTTPConfig: &commoncfg.HTTPClientConfig{},
364 },
365 test.CreateTmpl(t),
366 log.NewNopLogger(),
367 )
368 require.NoError(t, err)
369
370 encodedV2, err := notifierV2.encodeMessage(msgV2)
371 require.NoError(t, err)
372 require.Contains(t, encodedV2.String(), `"custom_details":{"error":"Custom details have been removed because the original event exceeds the maximum size of 512KB"}`)
373 }
374
375 func TestPagerDutyEmptySrcHref(t *testing.T) {
376 type pagerDutyEvent struct {
377 RoutingKey string `json:"routing_key"`
378 EventAction string `json:"event_action"`
379 DedupKey string `json:"dedup_key"`
380 Payload pagerDutyPayload `json:"payload"`
381 Images []pagerDutyImage
382 Links []pagerDutyLink
383 }
384
385 images := []config.PagerdutyImage{
386 {
387 Src: "",
388 Alt: "Empty src",
389 Href: "https://example.com/",
390 },
391 {
392 Src: "https://example.com/cat.jpg",
393 Alt: "Empty href",
394 Href: "",
395 },
396 {
397 Src: "https://example.com/cat.jpg",
398 Alt: "",
399 Href: "https://example.com/",
400 },
401 }
402
403 links := []config.PagerdutyLink{
404 {
405 Href: "",
406 Text: "Empty href",
407 },
408 {
409 Href: "https://example.com/",
410 Text: "",
411 },
412 }
413
414 expectedImages := make([]pagerDutyImage, 0, len(images))
415 for _, image := range images {
416 if image.Src == "" {
417 continue
418 }
419 expectedImages = append(expectedImages, pagerDutyImage{
420 Src: image.Src,
421 Alt: image.Alt,
422 Href: image.Href,
423 })
424 }
425
426 expectedLinks := make([]pagerDutyLink, 0, len(links))
427 for _, link := range links {
428 if link.Href == "" {
429 continue
430 }
431 expectedLinks = append(expectedLinks, pagerDutyLink{
432 HRef: link.Href,
433 Text: link.Text,
434 })
435 }
436
437 server := httptest.NewServer(http.HandlerFunc(
438 func(w http.ResponseWriter, r *http.Request) {
439 decoder := json.NewDecoder(r.Body)
440 var event pagerDutyEvent
441 if err := decoder.Decode(&event); err != nil {
442 panic(err)
443 }
444
445 if event.RoutingKey == "" || event.EventAction == "" {
446 http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
447 return
448 }
449
450 for _, image := range event.Images {
451 if image.Src == "" {
452 http.Error(w, "Event object is invalid: 'image src' is missing or blank", http.StatusBadRequest)
453 return
454 }
455 }
456
457 for _, link := range event.Links {
458 if link.HRef == "" {
459 http.Error(w, "Event object is invalid: 'link href' is missing or blank", http.StatusBadRequest)
460 return
461 }
462 }
463
464 require.Equal(t, expectedImages, event.Images)
465 require.Equal(t, expectedLinks, event.Links)
466 },
467 ))
468 defer server.Close()
469
470 url, err := url.Parse(server.URL)
471 require.NoError(t, err)
472
473 pagerDutyConfig := config.PagerdutyConfig{
474 HTTPConfig: &commoncfg.HTTPClientConfig{},
475 RoutingKey: config.Secret("01234567890123456789012345678901"),
476 URL: &config.URL{URL: url},
477 Images: images,
478 Links: links,
479 }
480
481 pagerDuty, err := New(&pagerDutyConfig, test.CreateTmpl(t), log.NewNopLogger())
482 require.NoError(t, err)
483
484 ctx := context.Background()
485 ctx = notify.WithGroupKey(ctx, "1")
486
487 _, err = pagerDuty.Notify(ctx, []*types.Alert{
488 {
489 Alert: model.Alert{
490 Labels: model.LabelSet{
491 "lbl1": "val1",
492 },
493 StartsAt: time.Now(),
494 EndsAt: time.Now().Add(time.Hour),
495 },
496 },
497 }...)
498 require.NoError(t, err)
499 }
500
View as plain text