From 90d475a5cef808c523a861b0d6e426a2d63f3467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Berbain?= Date: Wed, 30 Jun 2021 10:05:37 +0200 Subject: [PATCH] OpsGenie notification backend (#280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * basic notification to opsgenie alert call Signed-off-by: Raphaël Berbain * review: change error message for opsgenie Signed-off-by: Romain Beuque <556072+rbeuque74@users.noreply.github.com> * lint Signed-off-by: Raphaël Berbain * make timeout configurable Signed-off-by: Raphaël Berbain Co-authored-by: Raphaël Berbain Co-authored-by: Romain Beuque <556072+rbeuque74@users.noreply.github.com> --- config/README.md | 12 +++++ go.mod | 8 ++-- go.sum | 23 ++++++--- pkg/notify/init/init.go | 16 +++++++ pkg/notify/opsgenie/opsgenie.go | 81 ++++++++++++++++++++++++++++++++ pkg/plugins/builtin/ping/ping.go | 2 +- utask.go | 7 +++ 7 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 pkg/notify/opsgenie/opsgenie.go diff --git a/config/README.md b/config/README.md index f2a0fc38..6cc7c4e9 100644 --- a/config/README.md +++ b/config/README.md @@ -45,6 +45,7 @@ postgres://user:pass@db/utask?sslmode=disable "completed_task_expiration": "720h", // default == 720h == 30 days // notify_config contains a map of named notification configurations, composed of a type and config data, // implemented notifiers include: + // - opsgenie (https://www.atlassian.com/software/opsgenie); available zones are: global, eu, sandbox // - tat (github.com/ovh/tat) // - slack webhook (https://api.slack.com/messaging/webhooks) // - generic webhook (custom URL, with HTTP POST method) @@ -53,6 +54,17 @@ postgres://user:pass@db/utask?sslmode=disable // - default_notification_strategy is the strategy that will apply, if none matched above // available strategies are: always, failure_only, silent "notify_config": { + "opsgenie-eu": { + "type": "opsgenie", + "config": { + "zone": "eu", + "api_key": "very-secret", + "timeout": "30s" + }, + "default_notification_strategy": { + "task_state_update": "failure_only" + } + }, "tat-internal": { "type": "tat", "config": { diff --git a/go.mod b/go.mod index aff31836..ed96b985 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/gin-gonic/gin v1.6.3 github.com/go-gorp/gorp v2.2.0+incompatible + github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020 github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/gofrs/uuid v3.3.0+incompatible github.com/golang/protobuf v1.4.0 // indirect @@ -31,6 +32,7 @@ require ( github.com/maxatome/go-testdeep v1.8.0 github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 // indirect github.com/mitchellh/reflectwalk v1.0.1 // indirect + github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.8 github.com/ovh/configstore v0.3.2 github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 github.com/ovh/symmecrypt v0.4.3 @@ -45,7 +47,6 @@ require ( github.com/santhosh-tekuri/jsonschema v1.2.4 github.com/sirupsen/logrus v1.6.0 github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect - github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cobra v0.0.6 github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -58,9 +59,8 @@ require ( github.com/ybriffa/go-http-digest-auth-client v0.6.3 github.com/ziutek/mymysql v1.5.4 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200904194848-62affa334b73 - golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a - golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect + golang.org/x/net v0.0.0-20201224014010-6772e930b67b + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/ini.v1 v1.46.0 // indirect diff --git a/go.sum b/go.sum index f84ea0ea..5c1ee8db 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020 h1:mdi6AbCEoKCA1xKCmp7UtRB5fvGFlP92PvlhxgdvXEw= +github.com/go-ping/ping v0.0.0-20210506233800-ff8be3320020/go.mod h1:KmHOjTUmJh/l04ukqPoBWPEZr9jwN05h5NXQl5C+DyY= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-retryablehttp v0.5.1 h1:Vsx5XKPqPs3M6sM4U4GWyUqFS8aBiL9U5gkgvpkg4SE= +github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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 github.com/nelsam/hel/v2 v2.3.2 h1:tXRsJBqRxj4ISSPCrXhbqF8sT+BXA/UaIvjhYjP5Bhk= github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.8 h1:qF/rRi8GSU2mjBXfJIyMj9GGmjedsV3Gm1uYbiGlCRk= +github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.8/go.mod h1:4OjcxgwdXzezqytxN534MooNmrxRD50geWZxTD7845s= github.com/ovh/configstore v0.3.2 h1:/kr1B27JVzW4Eiz20muZSnQ5UyizFjLy5+2CVfp/mKs= github.com/ovh/configstore v0.3.2/go.mod h1:bBc7U++7HXgf9lrtmmJb31DK3Tp+Zv8GaIn0Bjolv/o= 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 github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw= -github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= 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 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/notify/init/init.go b/pkg/notify/init/init.go index 5f92c31d..bba8cb58 100644 --- a/pkg/notify/init/init.go +++ b/pkg/notify/init/init.go @@ -8,6 +8,7 @@ import ( "github.com/ovh/configstore" "github.com/ovh/utask" "github.com/ovh/utask/pkg/notify" + "github.com/ovh/utask/pkg/notify/opsgenie" "github.com/ovh/utask/pkg/notify/slack" "github.com/ovh/utask/pkg/notify/tat" "github.com/ovh/utask/pkg/notify/webhook" @@ -34,6 +35,21 @@ func Init(store *configstore.Store) error { ncfg.DefaultNotificationStrategy = newncfg.DefaultNotificationStrategy switch ncfg.Type { + case opsgenie.Type: + f := utask.NotifyBackendOpsGenie{} + if err := json.Unmarshal(ncfg.Config, &f); err != nil { + return fmt.Errorf("%s: %s, %s: %s", errRetrieveCfg, ncfg.Type, name, err) + } + ogns, err := opsgenie.NewOpsGenieNotificationSender( + f.Zone, + f.APIKey, + f.Timeout, + ) + if err != nil { + return fmt.Errorf("Failed to instantiate tat notification sender: %s", err) + } + notify.RegisterSender(name, ogns, ncfg.DefaultNotificationStrategy, ncfg.TemplateNotificationStrategies) + case tat.Type: f := utask.NotifyBackendTat{} if err := json.Unmarshal(ncfg.Config, &f); err != nil { diff --git a/pkg/notify/opsgenie/opsgenie.go b/pkg/notify/opsgenie/opsgenie.go new file mode 100644 index 00000000..1a6bb4e3 --- /dev/null +++ b/pkg/notify/opsgenie/opsgenie.go @@ -0,0 +1,81 @@ +package opsgenie + +import ( + "context" + "time" + + "github.com/juju/errors" + "github.com/opsgenie/opsgenie-go-sdk-v2/alert" + "github.com/opsgenie/opsgenie-go-sdk-v2/client" + "github.com/ovh/utask/pkg/notify" +) + +const ( + // Type represents OpsGenie as notify backend + Type = "opsgenie" + + // Zone are opsgenie api zones + ZoneSandbox = "sandbox" + ZoneDefault = "global" + ZoneEU = "eu" +) + +// NotificationSender is a notify.NotificationSender implementation +// capable of sending formatted notifications over OpsGenie (https://www.atlassian.com/software/opsgenie) +type NotificationSender struct { + opsGenieZone string + opsGenieAPIKey string + opsGenieTimeout time.Duration + client *alert.Client +} + +// NewOpsGenieNotificationSender instantiates a NotificationSender +func NewOpsGenieNotificationSender(zone, apikey, timeout string) (*NotificationSender, error) { + zonesToAPIUrls := map[string]client.ApiUrl{ + ZoneDefault: client.API_URL, + ZoneEU: client.API_URL_EU, + ZoneSandbox: client.API_URL_SANDBOX, + } + apiURL, present := zonesToAPIUrls[zone] + if !present { + return nil, errors.NotFoundf("opsgenie zone %q", zone) + } + client, err := alert.NewClient(&client.Config{ + ApiKey: apikey, + OpsGenieAPIURL: apiURL, + }) + if err != nil { + return nil, err + } + timeoutDuration := 30 * time.Second + if timeout != "" { + timeoutDuration, err = time.ParseDuration(timeout) + if err != nil { + return nil, err + } + } + return &NotificationSender{ + opsGenieZone: zone, + opsGenieAPIKey: apikey, + opsGenieTimeout: timeoutDuration, + client: client, + }, nil +} + +// Send dispatches a notify.Message to OpsGenie +func (ns *NotificationSender) Send(m *notify.Message, name string) { + req := &alert.CreateAlertRequest{ + Message: m.MainMessage, + Description: m.MainMessage, + Details: m.Fields, + } + + ctx, cancel := context.WithTimeout(context.Background(), ns.opsGenieTimeout) + defer cancel() + + _, err := ns.client.Create(ctx, req) + if err != nil { + notify.WrappedSendError(Type, err.Error()) + return + } +} diff --git a/pkg/plugins/builtin/ping/ping.go b/pkg/plugins/builtin/ping/ping.go index 2130e84e..0706a281 100644 --- a/pkg/plugins/builtin/ping/ping.go +++ b/pkg/plugins/builtin/ping/ping.go @@ -7,7 +7,7 @@ import ( "strconv" "time" - ping "github.com/sparrc/go-ping" + ping "github.com/go-ping/ping" "github.com/ovh/utask/pkg/plugins/taskplugin" ) diff --git a/utask.go b/utask.go index 9451138f..c786d747 100644 --- a/utask.go +++ b/utask.go @@ -145,6 +145,13 @@ type TemplateNotificationStrategy struct { NotificationStrategy string `json:"notification_strategy"` // value can be `always`, `failure_only`, `silent` } +// NotifyBackendOpsGenie holds configuration for instantiating an OPsGenie notify client +type NotifyBackendOpsGenie struct { + Zone string `json:"zone"` + APIKey string `json:"api_key"` + Timeout string `json:"timeout"` +} + // NotifyBackendTat holds configuration for instantiating a Tat notify client type NotifyBackendTat struct { Username string `json:"username"`