Skip to content

Commit 90d475a

Browse files
rgbjrbeuque74
andauthored
OpsGenie notification backend (#280)
* basic notification to opsgenie alert call Signed-off-by: Raphaël Berbain <[email protected]> * review: change error message for opsgenie Signed-off-by: Romain Beuque <[email protected]> * lint Signed-off-by: Raphaël Berbain <[email protected]> * make timeout configurable Signed-off-by: Raphaël Berbain <[email protected]> Co-authored-by: Raphaël Berbain <[email protected]> Co-authored-by: Romain Beuque <[email protected]>
1 parent e3af415 commit 90d475a

File tree

7 files changed

+138
-11
lines changed

7 files changed

+138
-11
lines changed

config/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ postgres://user:pass@db/utask?sslmode=disable
4545
"completed_task_expiration": "720h", // default == 720h == 30 days
4646
// notify_config contains a map of named notification configurations, composed of a type and config data,
4747
// implemented notifiers include:
48+
// - opsgenie (https://www.atlassian.com/software/opsgenie); available zones are: global, eu, sandbox
4849
// - tat (github.com/ovh/tat)
4950
// - slack webhook (https://api.slack.com/messaging/webhooks)
5051
// - generic webhook (custom URL, with HTTP POST method)
@@ -53,6 +54,17 @@ postgres://user:pass@db/utask?sslmode=disable
5354
// - default_notification_strategy is the strategy that will apply, if none matched above
5455
// available strategies are: always, failure_only, silent
5556
"notify_config": {
57+
"opsgenie-eu": {
58+
"type": "opsgenie",
59+
"config": {
60+
"zone": "eu",
61+
"api_key": "very-secret",
62+
"timeout": "30s"
63+
},
64+
"default_notification_strategy": {
65+
"task_state_update": "failure_only"
66+
}
67+
},
5668
"tat-internal": {
5769
"type": "tat",
5870
"config": {

go.mod

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
1717
github.com/gin-gonic/gin v1.6.3
1818
github.com/go-gorp/gorp v2.2.0+incompatible
19+
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020
1920
github.com/go-sql-driver/mysql v1.4.1 // indirect
2021
github.com/gofrs/uuid v3.3.0+incompatible
2122
github.com/golang/protobuf v1.4.0 // indirect
@@ -31,6 +32,7 @@ require (
3132
github.com/maxatome/go-testdeep v1.8.0
3233
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 // indirect
3334
github.com/mitchellh/reflectwalk v1.0.1 // indirect
35+
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.8
3436
github.com/ovh/configstore v0.3.2
3537
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
3638
github.com/ovh/symmecrypt v0.4.3
@@ -45,7 +47,6 @@ require (
4547
github.com/santhosh-tekuri/jsonschema v1.2.4
4648
github.com/sirupsen/logrus v1.6.0
4749
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
48-
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c
4950
github.com/spf13/afero v1.2.2 // indirect
5051
github.com/spf13/cobra v0.0.6
5152
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@@ -58,9 +59,8 @@ require (
5859
github.com/ybriffa/go-http-digest-auth-client v0.6.3
5960
github.com/ziutek/mymysql v1.5.4 // indirect
6061
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
61-
golang.org/x/net v0.0.0-20200904194848-62affa334b73
62-
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
63-
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
62+
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
63+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
6464
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
6565
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
6666
gopkg.in/ini.v1 v1.46.0 // indirect

go.sum

+17-6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
7575
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
7676
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
7777
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
78+
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020 h1:mdi6AbCEoKCA1xKCmp7UtRB5fvGFlP92PvlhxgdvXEw=
79+
github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020/go.mod h1:KmHOjTUmJh/l04ukqPoBWPEZr9jwN05h5NXQl5C+DyY=
7880
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
7981
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
8082
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
@@ -127,6 +129,10 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
127129
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
128130
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
129131
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
132+
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
133+
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
134+
github.com/hashicorp/go-retryablehttp v0.5.1 h1:Vsx5XKPqPs3M6sM4U4GWyUqFS8aBiL9U5gkgvpkg4SE=
135+
github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
130136
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
131137
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
132138
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -234,6 +240,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
234240
github.com/nelsam/hel/v2 v2.3.2 h1:tXRsJBqRxj4ISSPCrXhbqF8sT+BXA/UaIvjhYjP5Bhk=
235241
github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w=
236242
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
243+
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.8 h1:qF/rRi8GSU2mjBXfJIyMj9GGmjedsV3Gm1uYbiGlCRk=
244+
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.8/go.mod h1:4OjcxgwdXzezqytxN534MooNmrxRD50geWZxTD7845s=
237245
github.com/ovh/configstore v0.3.2 h1:/kr1B27JVzW4Eiz20muZSnQ5UyizFjLy5+2CVfp/mKs=
238246
github.com/ovh/configstore v0.3.2/go.mod h1:bBc7U++7HXgf9lrtmmJb31DK3Tp+Zv8GaIn0Bjolv/o=
239247
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c=
@@ -291,8 +299,6 @@ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbd
291299
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
292300
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
293301
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
294-
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw=
295-
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
296302
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
297303
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
298304
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -368,14 +374,16 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
368374
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
369375
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
370376
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
377+
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
378+
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
371379
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
372380
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
373381
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
374382
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
375383
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
376384
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
377-
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
378-
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
385+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
386+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
379387
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
380388
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
381389
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -391,11 +399,14 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
391399
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
392400
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
393401
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
394-
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
395-
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
402+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
403+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
404+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
396405
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
397406
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
398407
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
408+
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
409+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
399410
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
400411
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
401412
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

pkg/notify/init/init.go

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/ovh/configstore"
99
"github.com/ovh/utask"
1010
"github.com/ovh/utask/pkg/notify"
11+
"github.com/ovh/utask/pkg/notify/opsgenie"
1112
"github.com/ovh/utask/pkg/notify/slack"
1213
"github.com/ovh/utask/pkg/notify/tat"
1314
"github.com/ovh/utask/pkg/notify/webhook"
@@ -34,6 +35,21 @@ func Init(store *configstore.Store) error {
3435
ncfg.DefaultNotificationStrategy = newncfg.DefaultNotificationStrategy
3536

3637
switch ncfg.Type {
38+
case opsgenie.Type:
39+
f := utask.NotifyBackendOpsGenie{}
40+
if err := json.Unmarshal(ncfg.Config, &f); err != nil {
41+
return fmt.Errorf("%s: %s, %s: %s", errRetrieveCfg, ncfg.Type, name, err)
42+
}
43+
ogns, err := opsgenie.NewOpsGenieNotificationSender(
44+
f.Zone,
45+
f.APIKey,
46+
f.Timeout,
47+
)
48+
if err != nil {
49+
return fmt.Errorf("Failed to instantiate tat notification sender: %s", err)
50+
}
51+
notify.RegisterSender(name, ogns, ncfg.DefaultNotificationStrategy, ncfg.TemplateNotificationStrategies)
52+
3753
case tat.Type:
3854
f := utask.NotifyBackendTat{}
3955
if err := json.Unmarshal(ncfg.Config, &f); err != nil {

pkg/notify/opsgenie/opsgenie.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package opsgenie
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/juju/errors"
8+
"github.com/opsgenie/opsgenie-go-sdk-v2/alert"
9+
"github.com/opsgenie/opsgenie-go-sdk-v2/client"
10+
"github.com/ovh/utask/pkg/notify"
11+
)
12+
13+
const (
14+
// Type represents OpsGenie as notify backend
15+
Type = "opsgenie"
16+
17+
// Zone are opsgenie api zones
18+
ZoneSandbox = "sandbox"
19+
ZoneDefault = "global"
20+
ZoneEU = "eu"
21+
)
22+
23+
// NotificationSender is a notify.NotificationSender implementation
24+
// capable of sending formatted notifications over OpsGenie (https://www.atlassian.com/software/opsgenie)
25+
type NotificationSender struct {
26+
opsGenieZone string
27+
opsGenieAPIKey string
28+
opsGenieTimeout time.Duration
29+
client *alert.Client
30+
}
31+
32+
// NewOpsGenieNotificationSender instantiates a NotificationSender
33+
func NewOpsGenieNotificationSender(zone, apikey, timeout string) (*NotificationSender, error) {
34+
zonesToAPIUrls := map[string]client.ApiUrl{
35+
ZoneDefault: client.API_URL,
36+
ZoneEU: client.API_URL_EU,
37+
ZoneSandbox: client.API_URL_SANDBOX,
38+
}
39+
apiURL, present := zonesToAPIUrls[zone]
40+
if !present {
41+
return nil, errors.NotFoundf("opsgenie zone %q", zone)
42+
}
43+
client, err := alert.NewClient(&client.Config{
44+
ApiKey: apikey,
45+
OpsGenieAPIURL: apiURL,
46+
})
47+
if err != nil {
48+
return nil, err
49+
}
50+
timeoutDuration := 30 * time.Second
51+
if timeout != "" {
52+
timeoutDuration, err = time.ParseDuration(timeout)
53+
if err != nil {
54+
return nil, err
55+
}
56+
}
57+
return &NotificationSender{
58+
opsGenieZone: zone,
59+
opsGenieAPIKey: apikey,
60+
opsGenieTimeout: timeoutDuration,
61+
client: client,
62+
}, nil
63+
}
64+
65+
// Send dispatches a notify.Message to OpsGenie
66+
func (ns *NotificationSender) Send(m *notify.Message, name string) {
67+
req := &alert.CreateAlertRequest{
68+
Message: m.MainMessage,
69+
Description: m.MainMessage,
70+
Details: m.Fields,
71+
}
72+
73+
ctx, cancel := context.WithTimeout(context.Background(), ns.opsGenieTimeout)
74+
defer cancel()
75+
76+
_, err := ns.client.Create(ctx, req)
77+
if err != nil {
78+
notify.WrappedSendError(Type, err.Error())
79+
return
80+
}
81+
}

pkg/plugins/builtin/ping/ping.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strconv"
88
"time"
99

10-
ping "github.com/sparrc/go-ping"
10+
ping "github.com/go-ping/ping"
1111

1212
"github.com/ovh/utask/pkg/plugins/taskplugin"
1313
)

utask.go

+7
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ type TemplateNotificationStrategy struct {
145145
NotificationStrategy string `json:"notification_strategy"` // value can be `always`, `failure_only`, `silent`
146146
}
147147

148+
// NotifyBackendOpsGenie holds configuration for instantiating an OPsGenie notify client
149+
type NotifyBackendOpsGenie struct {
150+
Zone string `json:"zone"`
151+
APIKey string `json:"api_key"`
152+
Timeout string `json:"timeout"`
153+
}
154+
148155
// NotifyBackendTat holds configuration for instantiating a Tat notify client
149156
type NotifyBackendTat struct {
150157
Username string `json:"username"`

0 commit comments

Comments
 (0)