From ff381ab768f887189d10799971da0e1f0b99d310 Mon Sep 17 00:00:00 2001 From: Sam Arnold Date: Wed, 26 May 2021 21:35:21 -0400 Subject: [PATCH] feat: enable new-style slack apps --- CHANGELOG.md | 1 + integrations/streamer_test.go | 12 +++++---- server/server_test.go | 35 +++++++++++++++++++++++++++ services/slack/config.go | 9 ++++++- services/slack/service.go | 27 +++++++++++++-------- services/slack/slacktest/slacktest.go | 8 +++--- 6 files changed, 73 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0152b763d..b41239f84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#2544](https://github.com/influxdata/kapacitor/pull/2544): flux tasks skeleton in Kapacitor - [#2555](https://github.com/influxdata/kapacitor/pull/2555): run flux tasks with built-in flux engine - [#2559](https://github.com/influxdata/kapacitor/pull/2559): kapacitor cli supports flux tasks +- [#2560](https://github.com/influxdata/kapacitor/pull/2560): enable new-style slack apps ## v1.5.9 [2021-04-01] diff --git a/integrations/streamer_test.go b/integrations/streamer_test.go index ec9c205a8..a0bf8782c 100644 --- a/integrations/streamer_test.go +++ b/integrations/streamer_test.go @@ -22,9 +22,6 @@ import ( "text/template" "time" - "github.com/influxdata/kapacitor/services/discord" - "github.com/influxdata/kapacitor/services/discord/discordtest" - "github.com/davecgh/go-spew/spew" "github.com/docker/docker/api/types/swarm" "github.com/google/go-cmp/cmp" @@ -45,6 +42,8 @@ import ( "github.com/influxdata/kapacitor/services/bigpanda" "github.com/influxdata/kapacitor/services/bigpanda/bigpandatest" "github.com/influxdata/kapacitor/services/diagnostic" + "github.com/influxdata/kapacitor/services/discord" + "github.com/influxdata/kapacitor/services/discord/discordtest" "github.com/influxdata/kapacitor/services/hipchat" "github.com/influxdata/kapacitor/services/hipchat/hipchattest" "github.com/influxdata/kapacitor/services/httppost" @@ -8713,6 +8712,7 @@ stream c2.Workspace = "company_private" c2.Enabled = true c2.URL = ts.URL + "/test/slack/url2" + c2.Token = "my_secret_token" c2.Channel = "#channel" d := diagService.NewSlackHandler().WithContext(keyvalue.KV("test", "slack")) sl, err := slack.NewService([]slack.Config{c1, c2}, d) @@ -8725,7 +8725,8 @@ stream exp := []interface{}{ slacktest.Request{ - URL: "/test/slack/url", + URL: "/test/slack/url", + AuthHeader: "", PostData: slacktest.PostData{ Channel: "@jim", Username: "kapacitor", @@ -8741,7 +8742,8 @@ stream }, }, slacktest.Request{ - URL: "/test/slack/url2", + URL: "/test/slack/url2", + AuthHeader: "Bearer my_secret_token", PostData: slacktest.PostData{ Channel: "#alerts", Username: "kapacitor", diff --git a/server/server_test.go b/server/server_test.go index b9103ad2b..da1fcec51 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -7982,6 +7982,7 @@ func TestServer_UpdateConfig(t *testing.T) { "global": true, "icon-emoji": "", "state-changes-only": false, + "token": false, "url": false, "username": "kapacitor", "ssl-ca": "", @@ -7991,6 +7992,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }}, }, @@ -8005,6 +8007,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": false, + "token": false, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8013,6 +8016,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, updates: []updateAction{ @@ -8025,6 +8029,7 @@ func TestServer_UpdateConfig(t *testing.T) { "channel": "#general", "username": slack.DefaultUsername, "url": "http://slack.example.com/secret-token", + "token": "my_other_secret", }, }, element: "company_private", @@ -8041,6 +8046,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": false, + "token": false, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8049,6 +8055,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, { @@ -8062,6 +8069,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": true, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8070,6 +8078,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }}, }, @@ -8084,6 +8093,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": true, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8092,6 +8102,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, }, @@ -8121,6 +8132,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": false, + "token": false, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8129,6 +8141,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, { @@ -8142,6 +8155,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": true, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8150,6 +8164,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, { @@ -8163,6 +8178,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": false, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8171,6 +8187,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, }, @@ -8186,6 +8203,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": false, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8194,6 +8212,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, }, @@ -8219,6 +8238,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": false, + "token": false, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8227,6 +8247,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, { @@ -8240,6 +8261,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": true, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8248,6 +8270,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, { @@ -8261,6 +8284,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": false, "username": "testbot", "ssl-ca": "", "ssl-cert": "", @@ -8269,6 +8293,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, }, @@ -8284,6 +8309,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": false, "username": "testbot", "ssl-ca": "", "ssl-cert": "", @@ -8292,6 +8318,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, }, @@ -8314,6 +8341,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": false, + "token": false, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8322,6 +8350,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, { @@ -8335,6 +8364,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": true, "username": "kapacitor", "ssl-ca": "", "ssl-cert": "", @@ -8343,6 +8373,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, { @@ -8356,6 +8387,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": false, "username": "", "ssl-ca": "", "ssl-cert": "", @@ -8364,6 +8396,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, }, @@ -8379,6 +8412,7 @@ func TestServer_UpdateConfig(t *testing.T) { "icon-emoji": "", "state-changes-only": false, "url": true, + "token": false, "username": "", "ssl-ca": "", "ssl-cert": "", @@ -8387,6 +8421,7 @@ func TestServer_UpdateConfig(t *testing.T) { }, Redacted: []string{ "url", + "token", }, }, }, diff --git a/services/slack/config.go b/services/slack/config.go index 3034649a7..1153d161c 100644 --- a/services/slack/config.go +++ b/services/slack/config.go @@ -16,8 +16,15 @@ type Config struct { Default bool `toml:"default" override:"default"` // ID assigned if multiple slack configs are given Workspace string `toml:"workspace" override:"workspace"` - // The Slack webhook URL, can be obtained by adding Incoming Webhook integration. + // The Slack webhook URL. + // For new-style slack apps, use "https://slack.com/api/chat.postMessage". + // For legacy webhooks (e.g. created at https://slack.com/services/new/incoming-webhook), use the webhook link. URL string `toml:"url" override:"url,redact"` + // The Slack OAuth token. + // For new-style slack apps, go to https://api.slack.com/apps -> your app -> 'OAuth & Permissions' to find + // the token. Ensure your app has 'chat:write' and 'chat:write.public' permissions. + // For legacy webhooks this can be left blank. + Token string `toml:"token" override:"token,redact"` // The default channel, can be overridden per alert. Channel string `toml:"channel" override:"channel"` // The username of the Slack bot. diff --git a/services/slack/service.go b/services/slack/service.go index a98fcfbc0..a36c1104c 100644 --- a/services/slack/service.go +++ b/services/slack/service.go @@ -7,7 +7,6 @@ import ( "io" "io/ioutil" "net/http" - "sync" "github.com/influxdata/kapacitor/alert" @@ -196,7 +195,7 @@ func (s *Service) Global() bool { } func (s *Service) StateChangesOnly() bool { - return s.defaultConfig().Global + return s.defaultConfig().StateChangesOnly } // slack attachment info @@ -235,7 +234,7 @@ func (s *Service) Test(options interface{}) error { } func (s *Service) Alert(workspace, channel, message, username, iconEmoji string, level alert.Level) error { - url, post, err := s.preparePost(workspace, channel, message, username, iconEmoji, level) + url, token, post, err := s.preparePost(workspace, channel, message, username, iconEmoji, level) if err != nil { return err } @@ -245,11 +244,20 @@ func (s *Service) Alert(workspace, channel, message, username, iconEmoji string, return err } - resp, err := client.Post(url, "application/json", post) + req, err := http.NewRequest("POST", url, post) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { body, err := ioutil.ReadAll(resp.Body) if err != nil { @@ -267,13 +275,13 @@ func (s *Service) Alert(workspace, channel, message, username, iconEmoji string, return nil } -func (s *Service) preparePost(workspace, channel, message, username, iconEmoji string, level alert.Level) (string, io.Reader, error) { +func (s *Service) preparePost(workspace, channel, message, username, iconEmoji string, level alert.Level) (string, string, io.Reader, error) { c, err := s.config(workspace) if err != nil { - return "", nil, err + return "", "", nil, err } if !c.Enabled { - return "", nil, errors.New("service is not enabled") + return "", "", nil, errors.New("service is not enabled") } if channel == "" { channel = c.Channel @@ -294,7 +302,6 @@ func (s *Service) preparePost(workspace, channel, message, username, iconEmoji s Mrkdwn_in: []string{"text"}, } postData := make(map[string]interface{}) - postData["as_user"] = false postData["channel"] = channel postData["text"] = "" postData["attachments"] = []attachment{a} @@ -313,10 +320,10 @@ func (s *Service) preparePost(workspace, channel, message, username, iconEmoji s enc := json.NewEncoder(&post) err = enc.Encode(postData) if err != nil { - return "", nil, err + return "", "", nil, err } - return c.URL, &post, nil + return c.URL, c.Token, &post, nil } type HandlerConfig struct { diff --git a/services/slack/slacktest/slacktest.go b/services/slack/slacktest/slacktest.go index 9157de67c..2b9f00bd2 100644 --- a/services/slack/slacktest/slacktest.go +++ b/services/slack/slacktest/slacktest.go @@ -19,7 +19,8 @@ func NewServer() *Server { s := new(Server) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { sr := Request{ - URL: r.URL.String(), + URL: r.URL.String(), + AuthHeader: r.Header.Get("Authorization"), } dec := json.NewDecoder(r.Body) dec.Decode(&sr.PostData) @@ -45,8 +46,9 @@ func (s *Server) Close() { } type Request struct { - URL string - PostData PostData + URL string + AuthHeader string + PostData PostData } type PostData struct {