1
2
3
4
5
6
7
8
9
10
11
12
13
14 package opsgenie
15
16 import (
17 "context"
18 "fmt"
19 "io"
20 "net/http"
21 "net/url"
22 "os"
23 "testing"
24 "time"
25
26 "github.com/go-kit/log"
27 commoncfg "github.com/prometheus/common/config"
28 "github.com/prometheus/common/model"
29 "github.com/stretchr/testify/require"
30
31 "github.com/prometheus/alertmanager/config"
32 "github.com/prometheus/alertmanager/notify"
33 "github.com/prometheus/alertmanager/notify/test"
34 "github.com/prometheus/alertmanager/types"
35 )
36
37 func TestOpsGenieRetry(t *testing.T) {
38 notifier, err := New(
39 &config.OpsGenieConfig{
40 HTTPConfig: &commoncfg.HTTPClientConfig{},
41 },
42 test.CreateTmpl(t),
43 log.NewNopLogger(),
44 )
45 require.NoError(t, err)
46
47 retryCodes := append(test.DefaultRetryCodes(), http.StatusTooManyRequests)
48 for statusCode, expected := range test.RetryTests(retryCodes) {
49 actual, _ := notifier.retrier.Check(statusCode, nil)
50 require.Equal(t, expected, actual, fmt.Sprintf("error on status %d", statusCode))
51 }
52 }
53
54 func TestOpsGenieRedactedURL(t *testing.T) {
55 ctx, u, fn := test.GetContextWithCancelingURL()
56 defer fn()
57
58 key := "key"
59 notifier, err := New(
60 &config.OpsGenieConfig{
61 APIURL: &config.URL{URL: u},
62 APIKey: config.Secret(key),
63 HTTPConfig: &commoncfg.HTTPClientConfig{},
64 },
65 test.CreateTmpl(t),
66 log.NewNopLogger(),
67 )
68 require.NoError(t, err)
69
70 test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
71 }
72
73 func TestGettingOpsGegineApikeyFromFile(t *testing.T) {
74 ctx, u, fn := test.GetContextWithCancelingURL()
75 defer fn()
76
77 key := "key"
78
79 f, err := os.CreateTemp("", "opsgenie_test")
80 require.NoError(t, err, "creating temp file failed")
81 _, err = f.WriteString(key)
82 require.NoError(t, err, "writing to temp file failed")
83
84 notifier, err := New(
85 &config.OpsGenieConfig{
86 APIURL: &config.URL{URL: u},
87 APIKeyFile: f.Name(),
88 HTTPConfig: &commoncfg.HTTPClientConfig{},
89 },
90 test.CreateTmpl(t),
91 log.NewNopLogger(),
92 )
93 require.NoError(t, err)
94
95 test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
96 }
97
98 func TestOpsGenie(t *testing.T) {
99 u, err := url.Parse("https://opsgenie/api")
100 if err != nil {
101 t.Fatalf("failed to parse URL: %v", err)
102 }
103 logger := log.NewNopLogger()
104 tmpl := test.CreateTmpl(t)
105
106 for _, tc := range []struct {
107 title string
108 cfg *config.OpsGenieConfig
109
110 expectedEmptyAlertBody string
111 expectedBody string
112 }{
113 {
114 title: "config without details",
115 cfg: &config.OpsGenieConfig{
116 NotifierConfig: config.NotifierConfig{
117 VSendResolved: true,
118 },
119 Message: `{{ .CommonLabels.Message }}`,
120 Description: `{{ .CommonLabels.Description }}`,
121 Source: `{{ .CommonLabels.Source }}`,
122 Responders: []config.OpsGenieConfigResponder{
123 {
124 Name: `{{ .CommonLabels.ResponderName1 }}`,
125 Type: `{{ .CommonLabels.ResponderType1 }}`,
126 },
127 {
128 Name: `{{ .CommonLabels.ResponderName2 }}`,
129 Type: `{{ .CommonLabels.ResponderType2 }}`,
130 },
131 },
132 Tags: `{{ .CommonLabels.Tags }}`,
133 Note: `{{ .CommonLabels.Note }}`,
134 Priority: `{{ .CommonLabels.Priority }}`,
135 Entity: `{{ .CommonLabels.Entity }}`,
136 Actions: `{{ .CommonLabels.Actions }}`,
137 APIKey: `{{ .ExternalURL }}`,
138 APIURL: &config.URL{URL: u},
139 HTTPConfig: &commoncfg.HTTPClientConfig{},
140 },
141 expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{},"source":""}
142 `,
143 expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
144 `,
145 },
146 {
147 title: "config with details",
148 cfg: &config.OpsGenieConfig{
149 NotifierConfig: config.NotifierConfig{
150 VSendResolved: true,
151 },
152 Message: `{{ .CommonLabels.Message }}`,
153 Description: `{{ .CommonLabels.Description }}`,
154 Source: `{{ .CommonLabels.Source }}`,
155 Details: map[string]string{
156 "Description": `adjusted {{ .CommonLabels.Description }}`,
157 },
158 Responders: []config.OpsGenieConfigResponder{
159 {
160 Name: `{{ .CommonLabels.ResponderName1 }}`,
161 Type: `{{ .CommonLabels.ResponderType1 }}`,
162 },
163 {
164 Name: `{{ .CommonLabels.ResponderName2 }}`,
165 Type: `{{ .CommonLabels.ResponderType2 }}`,
166 },
167 },
168 Tags: `{{ .CommonLabels.Tags }}`,
169 Note: `{{ .CommonLabels.Note }}`,
170 Priority: `{{ .CommonLabels.Priority }}`,
171 Entity: `{{ .CommonLabels.Entity }}`,
172 Actions: `{{ .CommonLabels.Actions }}`,
173 APIKey: `{{ .ExternalURL }}`,
174 APIURL: &config.URL{URL: u},
175 HTTPConfig: &commoncfg.HTTPClientConfig{},
176 },
177 expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
178 `,
179 expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"EscalationA","type":"escalation"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1","entity":"test-domain","actions":["doThis","doThat"]}
180 `,
181 },
182 {
183 title: "config with multiple teams",
184 cfg: &config.OpsGenieConfig{
185 NotifierConfig: config.NotifierConfig{
186 VSendResolved: true,
187 },
188 Message: `{{ .CommonLabels.Message }}`,
189 Description: `{{ .CommonLabels.Description }}`,
190 Source: `{{ .CommonLabels.Source }}`,
191 Details: map[string]string{
192 "Description": `adjusted {{ .CommonLabels.Description }}`,
193 },
194 Responders: []config.OpsGenieConfigResponder{
195 {
196 Name: `{{ .CommonLabels.ResponderName3 }}`,
197 Type: `{{ .CommonLabels.ResponderType3 }}`,
198 },
199 },
200 Tags: `{{ .CommonLabels.Tags }}`,
201 Note: `{{ .CommonLabels.Note }}`,
202 Priority: `{{ .CommonLabels.Priority }}`,
203 APIKey: `{{ .ExternalURL }}`,
204 APIURL: &config.URL{URL: u},
205 HTTPConfig: &commoncfg.HTTPClientConfig{},
206 },
207 expectedEmptyAlertBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"","details":{"Description":"adjusted "},"source":""}
208 `,
209 expectedBody: `{"alias":"6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b","message":"message","description":"description","details":{"Actions":"doThis,doThat","Description":"adjusted description","Entity":"test-domain","Message":"message","Note":"this is a note","Priority":"P1","ResponderName1":"TeamA","ResponderName2":"EscalationA","ResponderName3":"TeamA,TeamB","ResponderType1":"team","ResponderType2":"escalation","ResponderType3":"teams","Source":"http://prometheus","Tags":"tag1,tag2"},"source":"http://prometheus","responders":[{"name":"TeamA","type":"team"},{"name":"TeamB","type":"team"}],"tags":["tag1","tag2"],"note":"this is a note","priority":"P1"}
210 `,
211 },
212 } {
213 t.Run(tc.title, func(t *testing.T) {
214 notifier, err := New(tc.cfg, tmpl, logger)
215 require.NoError(t, err)
216
217 ctx := context.Background()
218 ctx = notify.WithGroupKey(ctx, "1")
219
220 expectedURL, _ := url.Parse("https://opsgenie/apiv2/alerts")
221
222
223 alert1 := &types.Alert{
224 Alert: model.Alert{
225 StartsAt: time.Now(),
226 EndsAt: time.Now().Add(time.Hour),
227 },
228 }
229
230 req, retry, err := notifier.createRequests(ctx, alert1)
231 require.NoError(t, err)
232 require.Len(t, req, 1)
233 require.Equal(t, true, retry)
234 require.Equal(t, expectedURL, req[0].URL)
235 require.Equal(t, "GenieKey http://am", req[0].Header.Get("Authorization"))
236 require.Equal(t, tc.expectedEmptyAlertBody, readBody(t, req[0]))
237
238
239 alert2 := &types.Alert{
240 Alert: model.Alert{
241 Labels: model.LabelSet{
242 "Message": "message",
243 "Description": "description",
244 "Source": "http://prometheus",
245 "ResponderName1": "TeamA",
246 "ResponderType1": "team",
247 "ResponderName2": "EscalationA",
248 "ResponderType2": "escalation",
249 "ResponderName3": "TeamA,TeamB",
250 "ResponderType3": "teams",
251 "Tags": "tag1,tag2",
252 "Note": "this is a note",
253 "Priority": "P1",
254 "Entity": "test-domain",
255 "Actions": "doThis,doThat",
256 },
257 StartsAt: time.Now(),
258 EndsAt: time.Now().Add(time.Hour),
259 },
260 }
261 req, retry, err = notifier.createRequests(ctx, alert2)
262 require.NoError(t, err)
263 require.Equal(t, true, retry)
264 require.Len(t, req, 1)
265 require.Equal(t, tc.expectedBody, readBody(t, req[0]))
266
267
268 tc.cfg.APIKey = "{{ kaput "
269 _, _, err = notifier.createRequests(ctx, alert2)
270 require.Error(t, err)
271 require.Equal(t, err.Error(), "templating error: template: :1: function \"kaput\" not defined")
272 })
273 }
274 }
275
276 func TestOpsGenieWithUpdate(t *testing.T) {
277 u, err := url.Parse("https://test-opsgenie-url")
278 require.NoError(t, err)
279 tmpl := test.CreateTmpl(t)
280 ctx := context.Background()
281 ctx = notify.WithGroupKey(ctx, "1")
282 opsGenieConfigWithUpdate := config.OpsGenieConfig{
283 Message: `{{ .CommonLabels.Message }}`,
284 Description: `{{ .CommonLabels.Description }}`,
285 UpdateAlerts: true,
286 APIKey: "test-api-key",
287 APIURL: &config.URL{URL: u},
288 HTTPConfig: &commoncfg.HTTPClientConfig{},
289 }
290 notifierWithUpdate, err := New(&opsGenieConfigWithUpdate, tmpl, log.NewNopLogger())
291 alert := &types.Alert{
292 Alert: model.Alert{
293 StartsAt: time.Now(),
294 EndsAt: time.Now().Add(time.Hour),
295 Labels: model.LabelSet{
296 "Message": "new message",
297 "Description": "new description",
298 },
299 },
300 }
301 require.NoError(t, err)
302 requests, retry, err := notifierWithUpdate.createRequests(ctx, alert)
303 require.NoError(t, err)
304 require.True(t, retry)
305 require.Len(t, requests, 3)
306
307 body0 := readBody(t, requests[0])
308 body1 := readBody(t, requests[1])
309 body2 := readBody(t, requests[2])
310 key, _ := notify.ExtractGroupKey(ctx)
311 alias := key.Hash()
312
313 require.Equal(t, requests[0].URL.String(), "https://test-opsgenie-url/v2/alerts")
314 require.NotEmpty(t, body0)
315
316 require.Equal(t, requests[1].URL.String(), fmt.Sprintf("https://test-opsgenie-url/v2/alerts/%s/message?identifierType=alias", alias))
317 require.Equal(t, body1, `{"message":"new message"}
318 `)
319 require.Equal(t, requests[2].URL.String(), fmt.Sprintf("https://test-opsgenie-url/v2/alerts/%s/description?identifierType=alias", alias))
320 require.Equal(t, body2, `{"description":"new description"}
321 `)
322 }
323
324 func readBody(t *testing.T, r *http.Request) string {
325 t.Helper()
326 body, err := io.ReadAll(r.Body)
327 require.NoError(t, err)
328 return string(body)
329 }
330
View as plain text