From 351bda1992a2ad1b11dc33bb6b8352a4b3bc8418 Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Sun, 2 Feb 2020 21:50:16 +0530 Subject: [PATCH 01/10] Add MS Teams support Signed-off-by: Prasad Ghangal --- cmd/botkube/main.go | 12 +++- comm_config.yaml | 7 ++ go.mod | 6 +- go.sum | 9 +++ pkg/bot/teams.go | 148 ++++++++++++++++++++++++++++++++++++++ pkg/bot/teams_notif.go | 151 +++++++++++++++++++++++++++++++++++++++ pkg/config/config.go | 13 +++- pkg/notify/mattermost.go | 2 +- pkg/notify/slack.go | 4 +- pkg/notify/webhook.go | 2 +- 10 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 pkg/bot/teams.go create mode 100644 pkg/bot/teams_notif.go diff --git a/cmd/botkube/main.go b/cmd/botkube/main.go index 783c4d88f..a0f575d0d 100644 --- a/cmd/botkube/main.go +++ b/cmd/botkube/main.go @@ -55,6 +55,9 @@ func startController() error { return fmt.Errorf("Error in loading configuration. Error:%s", err.Error()) } + // List notifiers + notifiers := notify.ListNotifiers(conf.Communications) + if conf.Communications.Slack.Enabled { log.Info("Starting slack bot") sb := bot.NewSlackBot(conf) @@ -67,8 +70,13 @@ func startController() error { go mb.Start() } - notifiers := notify.ListNotifiers(conf.Communications) - log.Infof("Notifier List: config=%#v list=%#v\n", conf.Communications, notifiers) + if conf.Communications.Teams.Enabled { + log.Logger.Info("Starting MS Teams bot") + tb := bot.NewTeamsBot(conf) + notifiers = append(notifiers, tb) + go tb.Start() + } + // Start upgrade notifier if conf.Settings.UpgradeNotifier { log.Info("Starting upgrade notifier") diff --git a/comm_config.yaml b/comm_config.yaml index 822b25a39..e67eeedfd 100644 --- a/comm_config.yaml +++ b/comm_config.yaml @@ -15,6 +15,13 @@ communications: team: 'MATTERMOST_TEAM' # Mattermost Team to configure with BotKube channel: 'MATTERMOST_CHANNEL' # Mattermost Channel for receiving BotKube alerts notiftype: short # Change notification type short/long you want to receive. notiftype is optional and Default notification type is short (if not specified) + + # Settings for MS Teams + teams: + enabled: false + appID: 'TEAMS_APP_ID' + appPassword: 'TEAMS_APP_PASSWORD' + notiftype: short # Settings for ELS elasticsearch: diff --git a/go.mod b/go.mod index 782d221bf..b71a6c0d5 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/gorilla/websocket v1.4.1 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/imdario/mergo v0.3.7 // indirect + github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/lib/pq v1.2.0 // indirect github.com/mattermost/gorp v2.0.0+incompatible // indirect @@ -30,6 +31,7 @@ require ( github.com/onsi/ginkgo v1.10.2 // indirect github.com/pborman/uuid v1.2.0 // indirect github.com/pelletier/go-toml v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.2.1 github.com/sha1sum/aws_signing_client v0.0.0-20200229211254-f7815c59d5c1 github.com/sirupsen/logrus v1.4.2 @@ -44,11 +46,11 @@ require ( gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - gopkg.in/yaml.v2 v2.2.4 + gopkg.in/yaml.v2 v2.2.8 k8s.io/api v0.17.0 k8s.io/apimachinery v0.17.0 k8s.io/client-go v0.17.0 k8s.io/kubectl v0.17.0 ) -go 1.13 +go 1.12 diff --git a/go.sum b/go.sum index ed1a30b1f..ddc9dec24 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0 h1:w3NnFcKR5241cfmQU5ZZAsf0xcpId6mWOupTvJlUX2U= @@ -172,6 +173,9 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/infracloudio/msbotbuilder-go v0.1.0 h1:52h4uhEev67TTv4n4HNlo9rxr1/LiHShwYTtNBu5r5U= +github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e h1:rr89i/xWiD/l+MtGXF+LRz2K/wQ/SV1FabVPrEmHr3s= +github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -198,6 +202,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM= +github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -257,6 +263,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -432,6 +439,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/bot/teams.go b/pkg/bot/teams.go new file mode 100644 index 000000000..a9750713e --- /dev/null +++ b/pkg/bot/teams.go @@ -0,0 +1,148 @@ +package bot + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/infracloudio/botkube/pkg/config" + "github.com/infracloudio/botkube/pkg/events" + "github.com/infracloudio/botkube/pkg/execute" + "github.com/infracloudio/botkube/pkg/logging" + "github.com/infracloudio/msbotbuilder-go/core" + coreActivity "github.com/infracloudio/msbotbuilder-go/core/activity" + "github.com/infracloudio/msbotbuilder-go/schema" +) + +const ( + defaultMsgPath = "/api/messages" + defaultPort = "3978" +) + +// Teams contains credentials to start Teams backend server +type Teams struct { + AppID string + AppPassword string + MessagePath string + Port string + AllowKubectl bool + RestrictAccess bool + ClusterName string + NotifType config.NotifType + Adapter core.Adapter + + ConversationRef schema.ConversationReference +} + +// NewTeamsBot returns Teams instance +func NewTeamsBot(c *config.Config) *Teams { + logging.Logger.Infof("Config:: %+v", c.Communications.Teams) + return &Teams{ + AppID: c.Communications.Teams.AppID, + AppPassword: c.Communications.Teams.AppPassword, + NotifType: c.Communications.Teams.NotifType, + MessagePath: defaultMsgPath, + Port: defaultPort, + AllowKubectl: c.Settings.AllowKubectl, + RestrictAccess: c.Settings.RestrictAccess, + ClusterName: c.Settings.ClusterName, + } +} + +// Start MS Teams server to serve messages from Teams client +func (t *Teams) Start() { + var err error + setting := core.AdapterSetting{ + AppID: t.AppID, + AppPassword: t.AppPassword, + } + t.Adapter, err = core.NewBotAdapter(setting) + if err != nil { + logging.Logger.Errorf("Failed Start teams bot. %+v", err) + return + } + http.HandleFunc(t.MessagePath, t.processActivity) + logging.Logger.Infof("Started MS Teams server on port %s", defaultPort) + logging.Logger.Errorf("Error in MS Teams server. %v", http.ListenAndServe(fmt.Sprintf(":%s", t.Port), nil)) +} + +func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { + ctx := context.Background() + activity, err := t.Adapter.ParseRequest(ctx, req) + if err != nil { + logging.Logger.Errorf("Failed to parse Teams request. %s", err.Error()) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + err = t.Adapter.ProcessActivity(ctx, activity, coreActivity.HandlerFuncs{ + OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { + actjson, _ := json.MarshalIndent(turn.Activity, "", " ") + logging.Logger.Debugf("Received activity: %s", actjson) + return turn.SendActivity(coreActivity.MsgOptionText(t.processMessage(turn.Activity))) + }, + }) + if err != nil { + logging.Logger.Errorf("Failed to process request. %s", err.Error()) + } +} + +func (t *Teams) processMessage(activity schema.Activity) string { + // Trim @BotKube prefix + msg := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(activity.Text), "BotKube")) + + // Parse "set default channel" command and set conversation reference + if msg == "set default channel" { + t.ConversationRef = coreActivity.GetCoversationReference(activity) + // Remove messageID from the ChannelID + if ID, ok := activity.ChannelData["teamsChannelId"]; ok { + t.ConversationRef.ChannelID = ID.(string) + t.ConversationRef.Conversation.ID = ID.(string) + } + return "Okay. I'll send notifications to this channel" + } + + // Multicluster is not supported for Teams + e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.ClusterName, true) + return fmt.Sprintf("```%s\n%s```", t.ClusterName, e.Execute()) +} + +func (t *Teams) SendEvent(event events.Event) error { + card := formatTeamsMessage(event, t.NotifType) + if err := t.sendProactiveMessage(card); err != nil { + logging.Logger.Errorf("Failed to send notification. %s", err.Error()) + } + logging.Logger.Debugf("Event successfully sent to MS Teams >> %+v", event) + return nil +} + +// SendMessage sends message to MsTeams +func (t *Teams) SendMessage(msg string) error { + err := t.Adapter.ProactiveMessage(context.TODO(), t.ConversationRef, coreActivity.HandlerFuncs{ + OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { + return turn.SendActivity(coreActivity.MsgOptionText(msg)) + }, + }) + if err != nil { + return err + } + logging.Logger.Debug("Message successfully sent to MS Teams") + return nil +} + +func (t *Teams) sendProactiveMessage(card map[string]interface{}) error { + err := t.Adapter.ProactiveMessage(context.TODO(), t.ConversationRef, coreActivity.HandlerFuncs{ + OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { + attachments := []schema.Attachment{ + { + ContentType: "application/vnd.microsoft.card.adaptive", + Content: card, + }, + } + return turn.SendActivity(coreActivity.MsgOptionAttachments(attachments)) + }, + }) + return err +} diff --git a/pkg/bot/teams_notif.go b/pkg/bot/teams_notif.go new file mode 100644 index 000000000..499d057ec --- /dev/null +++ b/pkg/bot/teams_notif.go @@ -0,0 +1,151 @@ +package bot + +import ( + "strings" + + "github.com/infracloudio/botkube/pkg/config" + "github.com/infracloudio/botkube/pkg/events" + "github.com/infracloudio/botkube/pkg/notify" +) + +const ( + // Constants for sending messageCards + messageType = "MessageCard" + schemaContext = "http://schema.org/extensions" +) + +var themeColor = map[events.Level]string{ + events.Info: "good", + events.Warn: "warning", + events.Debug: "good", + events.Error: "attention", + events.Critical: "attention", +} + +type Fact map[string]interface{} + +func formatTeamsMessage(event events.Event, notifType config.NotifType) map[string]interface{} { + switch notifType { + case config.LongNotify: + return teamsLongNotification(event) + + case config.ShortNotify: + fallthrough + + default: + return teamsShortNotification(event) + } + return nil +} + +func teamsShortNotification(event events.Event) map[string]interface{} { + return map[string]interface{}{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": []map[string]interface{}{ + { + "type": "TextBlock", + "text": event.Title, + "size": "Large", + "color": themeColor[event.Level], + "wrap": true, + }, + { + "type": "TextBlock", + "text": strings.ReplaceAll(notify.FormatShortMessage(event), "```", ""), + "wrap": true, + }, + }, + } +} + +func teamsLongNotification(event events.Event) map[string]interface{} { + card := map[string]interface{}{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + } + + sectionFacts := []Fact{} + + if event.Cluster != "" { + sectionFacts = append(sectionFacts, Fact{ + "title": "Cluster", + "value": event.Cluster, + }) + } + + sectionFacts = append(sectionFacts, Fact{ + "title": "Name", + "value": event.Name, + }) + + if event.Namespace != "" { + sectionFacts = append(sectionFacts, Fact{ + "title": "Namespace", + "value": event.Namespace, + }) + } + + if event.Reason != "" { + sectionFacts = append(sectionFacts, Fact{ + "title": "Reason", + "value": event.Reason, + }) + } + + if len(event.Messages) > 0 { + message := "" + for _, m := range event.Messages { + message = message + m + "\n" + } + sectionFacts = append(sectionFacts, Fact{ + "title": "Message", + "value": message, + }) + } + + if event.Action != "" { + sectionFacts = append(sectionFacts, Fact{ + "title": "Action", + "value": event.Action, + }) + } + + if len(event.Recommendations) > 0 { + rec := "" + for _, r := range event.Recommendations { + rec = rec + r + "\n" + } + sectionFacts = append(sectionFacts, Fact{ + "title": "Recommendations", + "value": rec, + }) + } + + if len(event.Warnings) > 0 { + warn := "" + for _, w := range event.Warnings { + warn = warn + w + "\n" + } + sectionFacts = append(sectionFacts, Fact{ + "title": "Warnings", + "value": warn, + }) + } + + card["body"] = []map[string]interface{}{ + { + "type": "TextBlock", + "text": event.Title, + "size": "Large", + "color": themeColor[event.Level], + }, + { + "type": "FactSet", + "facts": sectionFacts, + }, + } + return card +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 46806ddeb..0bbf40d55 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -120,9 +120,10 @@ type Namespaces struct { // CommunicationsConfig channels to send events to type CommunicationsConfig struct { Slack Slack - ElasticSearch ElasticSearch Mattermost Mattermost Webhook Webhook + Teams Teams + ElasticSearch ElasticSearch } // Slack configuration to authentication and send notifications @@ -168,6 +169,16 @@ type Mattermost struct { NotifType NotifType `yaml:",omitempty"` } +// Teams creds for authentication with MS Teams +type Teams struct { + Enabled bool + AppID string `yaml:"appID,omitempty"` + AppPassword string `yaml:"appPassword,omitempty"` + Team string + Channel string + NotifType NotifType `yaml:",omitempty"` +} + // Webhook configuration to send notifications type Webhook struct { Enabled bool diff --git a/pkg/notify/mattermost.go b/pkg/notify/mattermost.go index 199da3251..1036765ea 100644 --- a/pkg/notify/mattermost.go +++ b/pkg/notify/mattermost.go @@ -211,7 +211,7 @@ func mmLongNotification(event events.Event) []*model.SlackAttachmentField { func mmShortNotification(event events.Event) []*model.SlackAttachmentField { return []*model.SlackAttachmentField{ { - Value: formatShortMessage(event), + Value: FormatShortMessage(event), }, } } diff --git a/pkg/notify/slack.go b/pkg/notify/slack.go index 734d4d183..606e57ed3 100644 --- a/pkg/notify/slack.go +++ b/pkg/notify/slack.go @@ -210,14 +210,14 @@ func slackShortNotification(event events.Event) slack.Attachment { Title: event.Title, Fields: []slack.AttachmentField{ { - Value: formatShortMessage(event), + Value: FormatShortMessage(event), }, }, Footer: "BotKube", } } -func formatShortMessage(event events.Event) (msg string) { +func FormatShortMessage(event events.Event) (msg string) { additionalMsg := "" if len(event.Messages) > 0 { for _, m := range event.Messages { diff --git a/pkg/notify/webhook.go b/pkg/notify/webhook.go index aa0ebd270..11f2c7a0e 100644 --- a/pkg/notify/webhook.go +++ b/pkg/notify/webhook.go @@ -86,7 +86,7 @@ func (w *Webhook) SendEvent(event events.Event) (err error) { Error: event.Error, Messages: event.Messages, }, - EventSummary: formatShortMessage(event), + EventSummary: FormatShortMessage(event), TimeStamp: event.TimeStamp, Recommendations: event.Recommendations, Warnings: event.Warnings, From 7bc0483b8c5303f0dfac260fa6d45426c47e4d0b Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Sun, 29 Mar 2020 17:54:33 +0530 Subject: [PATCH 02/10] Update msbotbuilder-go sdk dep to v0.2.0 Signed-off-by: Prasad Ghangal --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b71a6c0d5..d656e1cd1 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/gorilla/websocket v1.4.1 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/imdario/mergo v0.3.7 // indirect - github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e + github.com/infracloudio/msbotbuilder-go v0.2.0 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/lib/pq v1.2.0 // indirect github.com/mattermost/gorp v2.0.0+incompatible // indirect diff --git a/go.sum b/go.sum index ddc9dec24..3efa131d6 100644 --- a/go.sum +++ b/go.sum @@ -176,6 +176,8 @@ github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeY github.com/infracloudio/msbotbuilder-go v0.1.0 h1:52h4uhEev67TTv4n4HNlo9rxr1/LiHShwYTtNBu5r5U= github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e h1:rr89i/xWiD/l+MtGXF+LRz2K/wQ/SV1FabVPrEmHr3s= github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= +github.com/infracloudio/msbotbuilder-go v0.2.0 h1:Tnoc04aJ7zIyxa6f6OlhF+kclUcGJ5JDiJZViC7YpYk= +github.com/infracloudio/msbotbuilder-go v0.2.0/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= From 59747d95f8646303358c58397ca5f934f96ea801 Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Sun, 12 Apr 2020 19:50:30 +0530 Subject: [PATCH 03/10] Add support to upload long response as a file Signed-off-by: Prasad Ghangal --- go.mod | 2 +- go.sum | 3 + pkg/bot/teams.go | 248 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 223 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index d656e1cd1..90615bd7d 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/gorilla/websocket v1.4.1 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/imdario/mergo v0.3.7 // indirect - github.com/infracloudio/msbotbuilder-go v0.2.0 + github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/lib/pq v1.2.0 // indirect github.com/mattermost/gorp v2.0.0+incompatible // indirect diff --git a/go.sum b/go.sum index 3efa131d6..cd6521772 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -178,6 +179,8 @@ github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e h1: github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= github.com/infracloudio/msbotbuilder-go v0.2.0 h1:Tnoc04aJ7zIyxa6f6OlhF+kclUcGJ5JDiJZViC7YpYk= github.com/infracloudio/msbotbuilder-go v0.2.0/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= +github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf h1:oRSRni/lJYTELBzNprJAn291ZkpjbbKAMKU4OJV4blg= +github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= diff --git a/pkg/bot/teams.go b/pkg/bot/teams.go index a9750713e..bbd0179d3 100644 --- a/pkg/bot/teams.go +++ b/pkg/bot/teams.go @@ -1,10 +1,31 @@ +// Copyright (c) 2020 InfraCloud Technologies +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + package bot import ( + "bytes" "context" "encoding/json" "fmt" "net/http" + "net/url" "strings" "github.com/infracloudio/botkube/pkg/config" @@ -17,37 +38,57 @@ import ( ) const ( - defaultMsgPath = "/api/messages" - defaultPort = "3978" + defaultMsgPath = "/api/messages" + defaultPort = "3978" + consentBufferSize = 100 + longRespNotice = "Response is too long. Sending last few lines. Please send DM to BotKube to get complete response." + convTypePersonal = "personal" + channelSetCmd = "set default channel" + maxMessageSize = 15700 ) +var _ Bot = (*Teams)(nil) + // Teams contains credentials to start Teams backend server type Teams struct { - AppID string - AppPassword string - MessagePath string - Port string - AllowKubectl bool - RestrictAccess bool - ClusterName string - NotifType config.NotifType - Adapter core.Adapter - - ConversationRef schema.ConversationReference + AppID string + AppPassword string + MessagePath string + Port string + AllowKubectl bool + RestrictAccess bool + ClusterName string + NotifType config.NotifType + Adapter core.Adapter + ProcessedConsents chan processedConsent + CleanupDone chan bool + + ConversationRef *schema.ConversationReference +} + +type processedConsent struct { + ID string + conversationRef schema.ConversationReference +} + +type ConsentContext struct { + Command string } // NewTeamsBot returns Teams instance func NewTeamsBot(c *config.Config) *Teams { logging.Logger.Infof("Config:: %+v", c.Communications.Teams) return &Teams{ - AppID: c.Communications.Teams.AppID, - AppPassword: c.Communications.Teams.AppPassword, - NotifType: c.Communications.Teams.NotifType, - MessagePath: defaultMsgPath, - Port: defaultPort, - AllowKubectl: c.Settings.AllowKubectl, - RestrictAccess: c.Settings.RestrictAccess, - ClusterName: c.Settings.ClusterName, + AppID: c.Communications.Teams.AppID, + AppPassword: c.Communications.Teams.AppPassword, + NotifType: c.Communications.Teams.NotifType, + MessagePath: defaultMsgPath, + Port: defaultPort, + AllowKubectl: c.Settings.AllowKubectl, + RestrictAccess: c.Settings.RestrictAccess, + ClusterName: c.Settings.ClusterName, + ProcessedConsents: make(chan processedConsent, consentBufferSize), + CleanupDone: make(chan bool), } } @@ -63,9 +104,26 @@ func (t *Teams) Start() { logging.Logger.Errorf("Failed Start teams bot. %+v", err) return } + // Start consent cleanup + go t.cleanupConsents() http.HandleFunc(t.MessagePath, t.processActivity) logging.Logger.Infof("Started MS Teams server on port %s", defaultPort) logging.Logger.Errorf("Error in MS Teams server. %v", http.ListenAndServe(fmt.Sprintf(":%s", t.Port), nil)) + t.CleanupDone <- true +} + +func (t *Teams) cleanupConsents() { + for { + select { + case consent := <-t.ProcessedConsents: + fmt.Printf("Deleting activity %s\n", consent.ID) + if err := t.Adapter.DeleteActivity(context.Background(), consent.ID, consent.conversationRef); err != nil { + logging.Logger.Errorf("Failed to delete activity. %s", err.Error()) + } + case <-t.CleanupDone: + return + } + } } func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { @@ -79,9 +137,96 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { err = t.Adapter.ProcessActivity(ctx, activity, coreActivity.HandlerFuncs{ OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { - actjson, _ := json.MarshalIndent(turn.Activity, "", " ") - logging.Logger.Debugf("Received activity: %s", actjson) - return turn.SendActivity(coreActivity.MsgOptionText(t.processMessage(turn.Activity))) + //actjson, _ := json.MarshalIndent(turn.Activity, "", " ") + //logging.Logger.Debugf("Received activity: %s", actjson) + resp := t.processMessage(turn.Activity) + if len(resp) >= maxMessageSize { + if turn.Activity.Conversation.ConversationType == convTypePersonal { + // send file upload request + attachments := []schema.Attachment{ + { + ContentType: "application/vnd.microsoft.teams.card.file.consent", + Name: "response.txt", + Content: map[string]interface{}{ + "description": turn.Activity.Text, + "sizeInBytes": len(resp), + "acceptContext": map[string]interface{}{ + "command": activity.Text, + }, + }, + }, + } + return turn.SendActivity(coreActivity.MsgOptionAttachments(attachments)) + } + resp = fmt.Sprintf("%s\n```\nCluster: %s\n%s", longRespNotice, t.ClusterName, resp[len(resp)-maxMessageSize:]) + } + return turn.SendActivity(coreActivity.MsgOptionText(resp)) + }, + + // handle invoke events + // https://developer.microsoft.com/en-us/microsoft-teams/blogs/working-with-files-in-your-microsoft-teams-bot/ + OnInvokeFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { + t.pushProcessedConsent(turn.Activity.ReplyToID, coreActivity.GetCoversationReference(turn.Activity)) + if err != nil { + return schema.Activity{}, fmt.Errorf("failed to read file: %s", err.Error()) + } + if turn.Activity.Value["type"] != "fileUpload" { + return schema.Activity{}, nil + } + if turn.Activity.Value["action"] != "accept" { + return schema.Activity{}, nil + } + if turn.Activity.Value["context"] == nil { + return schema.Activity{}, nil + } + + // Parse upload info from invoke accept response + uploadInfo := schema.UploadInfo{} + infoJSON, err := json.Marshal(turn.Activity.Value["uploadInfo"]) + if err != nil { + return schema.Activity{}, err + } + if err := json.Unmarshal(infoJSON, &uploadInfo); err != nil { + return schema.Activity{}, err + } + + // Parse context + consentCtx := ConsentContext{} + ctxJSON, err := json.Marshal(turn.Activity.Value["context"]) + if err != nil { + return schema.Activity{}, err + } + if err := json.Unmarshal(ctxJSON, &consentCtx); err != nil { + return schema.Activity{}, err + } + + msg := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(consentCtx.Command), "BotKube")) + e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.ClusterName, true) + out := e.Execute() + + aj, _ := json.MarshalIndent(turn.Activity, "", " ") + fmt.Printf("Incoming Activity:: \n%s\n", aj) + + // upload file + err = t.putRequest(uploadInfo.UploadURL, []byte(out)) + if err != nil { + return schema.Activity{}, fmt.Errorf("failed to upload file: %s", err.Error()) + } + + // notify user about uploaded file + fileAttach := []schema.Attachment{ + { + ContentType: "application/vnd.microsoft.teams.card.file.info", + ContentURL: uploadInfo.ContentURL, + Name: uploadInfo.Name, + Content: map[string]interface{}{ + "uniqueId": uploadInfo.UniqueID, + "fileType": uploadInfo.FileType, + }, + }, + } + + return turn.SendActivity(coreActivity.MsgOptionAttachments(fileAttach)) }, }) if err != nil { @@ -94,8 +239,9 @@ func (t *Teams) processMessage(activity schema.Activity) string { msg := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(activity.Text), "BotKube")) // Parse "set default channel" command and set conversation reference - if msg == "set default channel" { - t.ConversationRef = coreActivity.GetCoversationReference(activity) + if msg == channelSetCmd { + ref := coreActivity.GetCoversationReference(activity) + t.ConversationRef = &ref // Remove messageID from the ChannelID if ID, ok := activity.ChannelData["teamsChannelId"]; ok { t.ConversationRef.ChannelID = ID.(string) @@ -106,7 +252,43 @@ func (t *Teams) processMessage(activity schema.Activity) string { // Multicluster is not supported for Teams e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.ClusterName, true) - return fmt.Sprintf("```%s\n%s```", t.ClusterName, e.Execute()) + out := e.Execute() + return fmt.Sprintf("```%s```", out) +} + +func (t *Teams) pushProcessedConsent(ID string, ref schema.ConversationReference) { + select { + case t.ProcessedConsents <- processedConsent{ID: ID, conversationRef: ref}: + break + default: + // Remove older ID if buffer is full + <-t.ProcessedConsents + t.ProcessedConsents <- processedConsent{ID: ID, conversationRef: ref} + } +} + +func (t *Teams) putRequest(u string, data []byte) error { + client := &http.Client{} + dec, err := url.QueryUnescape(u) + if err != nil { + return err + } + req, err := http.NewRequest(http.MethodPut, dec, bytes.NewBuffer(data)) + if err != nil { + return err + } + size := fmt.Sprintf("%d", len(data)) + req.Header.Set("Content-Type", "text/plain") + req.Header.Set("Content-Length", size) + req.Header.Set("Content-Range", fmt.Sprintf("bytes 0-%d/%d", len(data)-1, len(data))) + resp, err := client.Do(req) + if err != nil { + return err + } + if resp.StatusCode != 201 && resp.StatusCode != 200 { + return fmt.Errorf("failed to upload file with status %d", resp.StatusCode) + } + return nil } func (t *Teams) SendEvent(event events.Event) error { @@ -120,7 +302,11 @@ func (t *Teams) SendEvent(event events.Event) error { // SendMessage sends message to MsTeams func (t *Teams) SendMessage(msg string) error { - err := t.Adapter.ProactiveMessage(context.TODO(), t.ConversationRef, coreActivity.HandlerFuncs{ + if t.ConversationRef == nil { + logging.Logger.Infof("Skipping SendMessage since conversation ref not set") + return nil + } + err := t.Adapter.ProactiveMessage(context.TODO(), *t.ConversationRef, coreActivity.HandlerFuncs{ OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { return turn.SendActivity(coreActivity.MsgOptionText(msg)) }, @@ -133,7 +319,11 @@ func (t *Teams) SendMessage(msg string) error { } func (t *Teams) sendProactiveMessage(card map[string]interface{}) error { - err := t.Adapter.ProactiveMessage(context.TODO(), t.ConversationRef, coreActivity.HandlerFuncs{ + if t.ConversationRef == nil { + logging.Logger.Infof("Skipping SendMessage since conversation ref not set") + return nil + } + err := t.Adapter.ProactiveMessage(context.TODO(), *t.ConversationRef, coreActivity.HandlerFuncs{ OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { attachments := []schema.Attachment{ { From e4bdd9da1f5cf88ecc38f1f30e6bc5ceb18f4aad Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Mon, 13 Apr 2020 20:08:52 +0530 Subject: [PATCH 04/10] Set conv ref when "notifier start" command is executed Signed-off-by: Prasad Ghangal --- pkg/bot/teams.go | 112 ++++++++++++++++------------------------ pkg/execute/executor.go | 16 +++--- 2 files changed, 52 insertions(+), 76 deletions(-) diff --git a/pkg/bot/teams.go b/pkg/bot/teams.go index bbd0179d3..7139f6109 100644 --- a/pkg/bot/teams.go +++ b/pkg/bot/teams.go @@ -51,44 +51,36 @@ var _ Bot = (*Teams)(nil) // Teams contains credentials to start Teams backend server type Teams struct { - AppID string - AppPassword string - MessagePath string - Port string - AllowKubectl bool - RestrictAccess bool - ClusterName string - NotifType config.NotifType - Adapter core.Adapter - ProcessedConsents chan processedConsent - CleanupDone chan bool + AppID string + AppPassword string + MessagePath string + Port string + AllowKubectl bool + RestrictAccess bool + ClusterName string + NotifType config.NotifType + Adapter core.Adapter ConversationRef *schema.ConversationReference } -type processedConsent struct { - ID string - conversationRef schema.ConversationReference -} - type ConsentContext struct { Command string } // NewTeamsBot returns Teams instance func NewTeamsBot(c *config.Config) *Teams { - logging.Logger.Infof("Config:: %+v", c.Communications.Teams) + // Set notifier off by default + config.Notify = false return &Teams{ - AppID: c.Communications.Teams.AppID, - AppPassword: c.Communications.Teams.AppPassword, - NotifType: c.Communications.Teams.NotifType, - MessagePath: defaultMsgPath, - Port: defaultPort, - AllowKubectl: c.Settings.AllowKubectl, - RestrictAccess: c.Settings.RestrictAccess, - ClusterName: c.Settings.ClusterName, - ProcessedConsents: make(chan processedConsent, consentBufferSize), - CleanupDone: make(chan bool), + AppID: c.Communications.Teams.AppID, + AppPassword: c.Communications.Teams.AppPassword, + NotifType: c.Communications.Teams.NotifType, + MessagePath: defaultMsgPath, + Port: defaultPort, + AllowKubectl: c.Settings.AllowKubectl, + RestrictAccess: c.Settings.RestrictAccess, + ClusterName: c.Settings.ClusterName, } } @@ -105,24 +97,15 @@ func (t *Teams) Start() { return } // Start consent cleanup - go t.cleanupConsents() http.HandleFunc(t.MessagePath, t.processActivity) logging.Logger.Infof("Started MS Teams server on port %s", defaultPort) logging.Logger.Errorf("Error in MS Teams server. %v", http.ListenAndServe(fmt.Sprintf(":%s", t.Port), nil)) - t.CleanupDone <- true } -func (t *Teams) cleanupConsents() { - for { - select { - case consent := <-t.ProcessedConsents: - fmt.Printf("Deleting activity %s\n", consent.ID) - if err := t.Adapter.DeleteActivity(context.Background(), consent.ID, consent.conversationRef); err != nil { - logging.Logger.Errorf("Failed to delete activity. %s", err.Error()) - } - case <-t.CleanupDone: - return - } +func (t *Teams) deleteConsent(ID string, convRef schema.ConversationReference) { + logging.Logger.Debugf("Deleting activity %s\n", ID) + if err := t.Adapter.DeleteActivity(context.Background(), ID, convRef); err != nil { + logging.Logger.Errorf("Failed to delete activity. %s", err.Error()) } } @@ -137,8 +120,6 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { err = t.Adapter.ProcessActivity(ctx, activity, coreActivity.HandlerFuncs{ OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { - //actjson, _ := json.MarshalIndent(turn.Activity, "", " ") - //logging.Logger.Debugf("Received activity: %s", actjson) resp := t.processMessage(turn.Activity) if len(resp) >= maxMessageSize { if turn.Activity.Conversation.ConversationType == convTypePersonal { @@ -166,7 +147,7 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { // handle invoke events // https://developer.microsoft.com/en-us/microsoft-teams/blogs/working-with-files-in-your-microsoft-teams-bot/ OnInvokeFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { - t.pushProcessedConsent(turn.Activity.ReplyToID, coreActivity.GetCoversationReference(turn.Activity)) + t.deleteConsent(turn.Activity.ReplyToID, coreActivity.GetCoversationReference(turn.Activity)) if err != nil { return schema.Activity{}, fmt.Errorf("failed to read file: %s", err.Error()) } @@ -204,8 +185,8 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.ClusterName, true) out := e.Execute() - aj, _ := json.MarshalIndent(turn.Activity, "", " ") - fmt.Printf("Incoming Activity:: \n%s\n", aj) + actJSON, _ := json.MarshalIndent(turn.Activity, "", " ") + logging.Logger.Debugf("Incoming MSTeams Activity: %s", actJSON) // upload file err = t.putRequest(uploadInfo.UploadURL, []byte(out)) @@ -225,7 +206,6 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { }, }, } - return turn.SendActivity(coreActivity.MsgOptionAttachments(fileAttach)) }, }) @@ -238,33 +218,29 @@ func (t *Teams) processMessage(activity schema.Activity) string { // Trim @BotKube prefix msg := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(activity.Text), "BotKube")) - // Parse "set default channel" command and set conversation reference - if msg == channelSetCmd { - ref := coreActivity.GetCoversationReference(activity) - t.ConversationRef = &ref - // Remove messageID from the ChannelID - if ID, ok := activity.ChannelData["teamsChannelId"]; ok { - t.ConversationRef.ChannelID = ID.(string) - t.ConversationRef.Conversation.ID = ID.(string) + // User needs to execute "notifier start" cmd to enable notifications + // Parse "notifier" command and set conversation reference + args := strings.Fields(msg) + if activity.Conversation.ConversationType != convTypePersonal && len(args) > 0 && execute.ValidNotifierCommand[args[0]] { + if len(args) < 2 { + return execute.IncompleteCmdMsg + } + if execute.Start.String() == args[1] { + config.Notify = true + ref := coreActivity.GetCoversationReference(activity) + t.ConversationRef = &ref + // Remove messageID from the ChannelID + if ID, ok := activity.ChannelData["teamsChannelId"]; ok { + t.ConversationRef.ChannelID = ID.(string) + t.ConversationRef.Conversation.ID = ID.(string) + } + return fmt.Sprintf(execute.NotifierStartMsg, t.ClusterName) } - return "Okay. I'll send notifications to this channel" } // Multicluster is not supported for Teams e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.ClusterName, true) - out := e.Execute() - return fmt.Sprintf("```%s```", out) -} - -func (t *Teams) pushProcessedConsent(ID string, ref schema.ConversationReference) { - select { - case t.ProcessedConsents <- processedConsent{ID: ID, conversationRef: ref}: - break - default: - // Remove older ID if buffer is full - <-t.ProcessedConsents - t.ProcessedConsents <- processedConsent{ID: ID, conversationRef: ref} - } + return fmt.Sprintf("```\n%s\n```", e.Execute()) } func (t *Teams) putRequest(u string, data []byte) error { diff --git a/pkg/execute/executor.go b/pkg/execute/executor.go index 6b3049eee..d8771a91d 100644 --- a/pkg/execute/executor.go +++ b/pkg/execute/executor.go @@ -37,7 +37,7 @@ import ( ) var ( - validNotifierCommand = map[string]bool{ + ValidNotifierCommand = map[string]bool{ "notifier": true, } validPingCommand = map[string]bool{ @@ -65,10 +65,10 @@ var ( ) const ( - notifierStartMsg = "Brace yourselves, notifications are coming from cluster '%s'." + NotifierStartMsg = "Brace yourselves, notifications are coming from cluster '%s'." notifierStopMsg = "Sure! I won't send you notifications from cluster '%s' anymore." unsupportedCmdMsg = "Command not supported. Please run /botkubehelp to see supported commands." - incompleteCmdMsg = "You missed to pass options for the command. Please run /botkubehelp to see command options." + IncompleteCmdMsg = "You missed to pass options for the command. Please run /botkubehelp to see command options." kubectlDisabledMsg = "Sorry, the admin hasn't given me the permission to execute kubectl command on cluster '%s'." filterNameMissing = "You forgot to pass filter name. Please pass one of the following valid filters:\n\n%s" filterEnabled = "I have enabled '%s' filter on '%s' cluster." @@ -178,7 +178,7 @@ func (e *DefaultExecutor) Execute() string { return runKubectlCommand(args, e.ClusterName, e.DefaultNamespace, e.IsAuthChannel) } } - if validNotifierCommand[args[0]] { + if ValidNotifierCommand[args[0]] { return runNotifierCommand(args, e.ClusterName, e.IsAuthChannel) } if validPingCommand[args[0]] { @@ -275,14 +275,14 @@ func runNotifierCommand(args []string, clusterName string, isAuthChannel bool) s return "" } if len(args) < 2 { - return incompleteCmdMsg + return IncompleteCmdMsg } switch args[1] { case Start.String(): config.Notify = true - log.Info("Notifier enabled") - return fmt.Sprintf(notifierStartMsg, clusterName) + log.Logger.Info("Notifier enabled") + return fmt.Sprintf(NotifierStartMsg, clusterName) case Stop.String(): config.Notify = false log.Info("Notifier disabled") @@ -309,7 +309,7 @@ func runFilterCommand(args []string, clusterName string, isAuthChannel bool) str return "" } if len(args) < 2 { - return incompleteCmdMsg + return IncompleteCmdMsg } switch args[1] { From f3075039901ce10061f2cbbd7270dde422ab9121 Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Sun, 26 Jul 2020 17:42:50 +0530 Subject: [PATCH 05/10] Add custom message for help in teams integration Signed-off-by: Prasad Ghangal --- build/Dockerfile | 2 +- cmd/botkube/main.go | 3 +- comm_config.yaml | 7 +-- deploy-all-in-one.yaml | 9 +++- go.mod | 1 - go.sum | 16 +++--- pkg/bot/mattermost.go | 2 +- pkg/bot/slack.go | 4 +- pkg/bot/teams.go | 96 +++++++++++++++++++++--------------- pkg/bot/teams_notif.go | 12 ++--- pkg/config/config.go | 12 ++++- pkg/controller/controller.go | 8 +-- pkg/execute/executor.go | 29 +++++++---- 13 files changed, 124 insertions(+), 77 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index d801a0d1c..cc882760a 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -18,7 +18,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # Development image -FROM golang:1.12-alpine3.10 AS BUILD-ENV +FROM golang:1.13-alpine3.10 AS BUILD-ENV ARG GOOS_VAL ARG GOARCH_VAL diff --git a/cmd/botkube/main.go b/cmd/botkube/main.go index a0f575d0d..d5cb86193 100644 --- a/cmd/botkube/main.go +++ b/cmd/botkube/main.go @@ -30,6 +30,7 @@ import ( "github.com/infracloudio/botkube/pkg/metrics" "github.com/infracloudio/botkube/pkg/notify" "github.com/infracloudio/botkube/pkg/utils" + _ "k8s.io/client-go/plugin/pkg/client/auth" ) const ( @@ -71,7 +72,7 @@ func startController() error { } if conf.Communications.Teams.Enabled { - log.Logger.Info("Starting MS Teams bot") + log.Info("Starting MS Teams bot") tb := bot.NewTeamsBot(conf) notifiers = append(notifiers, tb) go tb.Start() diff --git a/comm_config.yaml b/comm_config.yaml index e67eeedfd..88b7bc93b 100644 --- a/comm_config.yaml +++ b/comm_config.yaml @@ -18,9 +18,10 @@ communications: # Settings for MS Teams teams: - enabled: false - appID: 'TEAMS_APP_ID' - appPassword: 'TEAMS_APP_PASSWORD' + enabled: true + appID: "4300ff5c-ec94-4f13-a0ed-b9bf4a9da064" + appPassword: "XkuXl18Bl6pU-=e7oZRkuDLdbERDzI-/" + port: 3978 notiftype: short # Settings for ELS diff --git a/deploy-all-in-one.yaml b/deploy-all-in-one.yaml index 755947471..58994d0bf 100644 --- a/deploy-all-in-one.yaml +++ b/deploy-all-in-one.yaml @@ -272,7 +272,14 @@ stringData: type: botkube-event shards: 1 replicas: 0 - + + # Settings for MS Teams + teams: + enabled: true + appID: "4300ff5c-ec94-4f13-a0ed-b9bf4a9da064" + appPassword: "XkuXl18Bl6pU-=e7oZRkuDLdbERDzI-/" + notiftype: short + # Settings for Webhook webhook: enabled: false diff --git a/go.mod b/go.mod index 90615bd7d..833607026 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,6 @@ require ( github.com/onsi/ginkgo v1.10.2 // indirect github.com/pborman/uuid v1.2.0 // indirect github.com/pelletier/go-toml v1.5.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.2.1 github.com/sha1sum/aws_signing_client v0.0.0-20200229211254-f7815c59d5c1 github.com/sirupsen/logrus v1.4.2 diff --git a/go.sum b/go.sum index cd6521772..6207acd0e 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,18 @@ cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -154,6 +160,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= @@ -172,15 +179,10 @@ github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/infracloudio/msbotbuilder-go v0.1.0 h1:52h4uhEev67TTv4n4HNlo9rxr1/LiHShwYTtNBu5r5U= -github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e h1:rr89i/xWiD/l+MtGXF+LRz2K/wQ/SV1FabVPrEmHr3s= -github.com/infracloudio/msbotbuilder-go v0.1.1-0.20200128183632-9d11322f671e/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= -github.com/infracloudio/msbotbuilder-go v0.2.0 h1:Tnoc04aJ7zIyxa6f6OlhF+kclUcGJ5JDiJZViC7YpYk= -github.com/infracloudio/msbotbuilder-go v0.2.0/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf h1:oRSRni/lJYTELBzNprJAn291ZkpjbbKAMKU4OJV4blg= github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= diff --git a/pkg/bot/mattermost.go b/pkg/bot/mattermost.go index 26eba9623..dfff8327a 100644 --- a/pkg/bot/mattermost.go +++ b/pkg/bot/mattermost.go @@ -150,7 +150,7 @@ func (mm *mattermostMessage) handleMessage(b MMBot) { mm.Request = strings.TrimPrefix(post.Message, "@"+BotName+" ") e := execute.NewDefaultExecutor(mm.Request, b.AllowKubectl, b.RestrictAccess, b.DefaultNamespace, - b.ClusterName, b.ChannelName, mm.IsAuthChannel) + b.ClusterName, config.MattermostBot, b.ChannelName, mm.IsAuthChannel) mm.Response = e.Execute() mm.sendMessage() } diff --git a/pkg/bot/slack.go b/pkg/bot/slack.go index b3627389f..6b2a35aa8 100644 --- a/pkg/bot/slack.go +++ b/pkg/bot/slack.go @@ -156,12 +156,12 @@ func (sm *slackMessage) HandleMessage(b *SlackBot) { } e := execute.NewDefaultExecutor(sm.Request, b.AllowKubectl, b.RestrictAccess, b.DefaultNamespace, - b.ClusterName, b.ChannelName, sm.IsAuthChannel) + b.ClusterName, config.SlackBot, b.ChannelName, sm.IsAuthChannel) sm.Response = e.Execute() sm.Send() } -func (sm slackMessage) Send() { +func (sm *slackMessage) Send() { // Upload message as a file if too long if len(sm.Response) >= 3990 { params := slack.FileUploadParameters{ diff --git a/pkg/bot/teams.go b/pkg/bot/teams.go index 7139f6109..1bd62135f 100644 --- a/pkg/bot/teams.go +++ b/pkg/bot/teams.go @@ -31,7 +31,7 @@ import ( "github.com/infracloudio/botkube/pkg/config" "github.com/infracloudio/botkube/pkg/events" "github.com/infracloudio/botkube/pkg/execute" - "github.com/infracloudio/botkube/pkg/logging" + "github.com/infracloudio/botkube/pkg/log" "github.com/infracloudio/msbotbuilder-go/core" coreActivity "github.com/infracloudio/msbotbuilder-go/core/activity" "github.com/infracloudio/msbotbuilder-go/schema" @@ -45,21 +45,29 @@ const ( convTypePersonal = "personal" channelSetCmd = "set default channel" maxMessageSize = 15700 + contentTypeCard = "application/vnd.microsoft.card.adaptive" + contentTypeFile = "application/vnd.microsoft.teams.card.file.consent" + responseFileName = "response.txt" + + activityFileUpload = "fileUpload" + activityAccept = "accept" + activityUploadInfo = "uploadInfo" ) var _ Bot = (*Teams)(nil) // Teams contains credentials to start Teams backend server type Teams struct { - AppID string - AppPassword string - MessagePath string - Port string - AllowKubectl bool - RestrictAccess bool - ClusterName string - NotifType config.NotifType - Adapter core.Adapter + AppID string + AppPassword string + MessagePath string + Port string + AllowKubectl bool + RestrictAccess bool + ClusterName string + NotifType config.NotifType + Adapter core.Adapter + DefaultNamespace string ConversationRef *schema.ConversationReference } @@ -72,15 +80,20 @@ type ConsentContext struct { func NewTeamsBot(c *config.Config) *Teams { // Set notifier off by default config.Notify = false + port := defaultPort + if c.Communications.Teams.Port != "" { + port = c.Communications.Teams.Port + } return &Teams{ - AppID: c.Communications.Teams.AppID, - AppPassword: c.Communications.Teams.AppPassword, - NotifType: c.Communications.Teams.NotifType, - MessagePath: defaultMsgPath, - Port: defaultPort, - AllowKubectl: c.Settings.AllowKubectl, - RestrictAccess: c.Settings.RestrictAccess, - ClusterName: c.Settings.ClusterName, + AppID: c.Communications.Teams.AppID, + AppPassword: c.Communications.Teams.AppPassword, + NotifType: c.Communications.Teams.NotifType, + MessagePath: defaultMsgPath, + Port: port, + AllowKubectl: c.Settings.Kubectl.Enabled, + RestrictAccess: c.Settings.Kubectl.RestrictAccess, + DefaultNamespace: c.Settings.Kubectl.DefaultNamespace, + ClusterName: c.Settings.ClusterName, } } @@ -93,27 +106,28 @@ func (t *Teams) Start() { } t.Adapter, err = core.NewBotAdapter(setting) if err != nil { - logging.Logger.Errorf("Failed Start teams bot. %+v", err) + log.Errorf("Failed Start teams bot. %+v", err) return } // Start consent cleanup http.HandleFunc(t.MessagePath, t.processActivity) - logging.Logger.Infof("Started MS Teams server on port %s", defaultPort) - logging.Logger.Errorf("Error in MS Teams server. %v", http.ListenAndServe(fmt.Sprintf(":%s", t.Port), nil)) + log.Infof("Started MS Teams server on port %s", defaultPort) + log.Errorf("Error in MS Teams server. %v", http.ListenAndServe(fmt.Sprintf(":%s", t.Port), nil)) } func (t *Teams) deleteConsent(ID string, convRef schema.ConversationReference) { - logging.Logger.Debugf("Deleting activity %s\n", ID) + log.Debugf("Deleting activity %s\n", ID) if err := t.Adapter.DeleteActivity(context.Background(), ID, convRef); err != nil { - logging.Logger.Errorf("Failed to delete activity. %s", err.Error()) + log.Errorf("Failed to delete activity. %s", err.Error()) } } func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { ctx := context.Background() + log.Debugf("Received activity %v\n", req) activity, err := t.Adapter.ParseRequest(ctx, req) if err != nil { - logging.Logger.Errorf("Failed to parse Teams request. %s", err.Error()) + log.Errorf("Failed to parse Teams request. %s", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -126,8 +140,8 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { // send file upload request attachments := []schema.Attachment{ { - ContentType: "application/vnd.microsoft.teams.card.file.consent", - Name: "response.txt", + ContentType: contentTypeFile, + Name: responseFileName, Content: map[string]interface{}{ "description": turn.Activity.Text, "sizeInBytes": len(resp), @@ -151,10 +165,10 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { if err != nil { return schema.Activity{}, fmt.Errorf("failed to read file: %s", err.Error()) } - if turn.Activity.Value["type"] != "fileUpload" { + if turn.Activity.Value["type"] != activityFileUpload { return schema.Activity{}, nil } - if turn.Activity.Value["action"] != "accept" { + if turn.Activity.Value["action"] != activityAccept { return schema.Activity{}, nil } if turn.Activity.Value["context"] == nil { @@ -163,7 +177,7 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { // Parse upload info from invoke accept response uploadInfo := schema.UploadInfo{} - infoJSON, err := json.Marshal(turn.Activity.Value["uploadInfo"]) + infoJSON, err := json.Marshal(turn.Activity.Value[activityUploadInfo]) if err != nil { return schema.Activity{}, err } @@ -182,11 +196,12 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { } msg := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(consentCtx.Command), "BotKube")) - e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.ClusterName, true) + e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.DefaultNamespace, + t.ClusterName, config.TeamsBot, "", true) out := e.Execute() actJSON, _ := json.MarshalIndent(turn.Activity, "", " ") - logging.Logger.Debugf("Incoming MSTeams Activity: %s", actJSON) + log.Debugf("Incoming MSTeams Activity: %s", actJSON) // upload file err = t.putRequest(uploadInfo.UploadURL, []byte(out)) @@ -197,7 +212,7 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { // notify user about uploaded file fileAttach := []schema.Attachment{ { - ContentType: "application/vnd.microsoft.teams.card.file.info", + ContentType: contentTypeFile, ContentURL: uploadInfo.ContentURL, Name: uploadInfo.Name, Content: map[string]interface{}{ @@ -210,7 +225,7 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { }, }) if err != nil { - logging.Logger.Errorf("Failed to process request. %s", err.Error()) + log.Errorf("Failed to process request. %s", err.Error()) } } @@ -239,7 +254,8 @@ func (t *Teams) processMessage(activity schema.Activity) string { } // Multicluster is not supported for Teams - e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.ClusterName, true) + e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.DefaultNamespace, + t.ClusterName, config.TeamsBot, "", true) return fmt.Sprintf("```\n%s\n```", e.Execute()) } @@ -270,16 +286,16 @@ func (t *Teams) putRequest(u string, data []byte) error { func (t *Teams) SendEvent(event events.Event) error { card := formatTeamsMessage(event, t.NotifType) if err := t.sendProactiveMessage(card); err != nil { - logging.Logger.Errorf("Failed to send notification. %s", err.Error()) + log.Errorf("Failed to send notification. %s", err.Error()) } - logging.Logger.Debugf("Event successfully sent to MS Teams >> %+v", event) + log.Debugf("Event successfully sent to MS Teams >> %+v", event) return nil } // SendMessage sends message to MsTeams func (t *Teams) SendMessage(msg string) error { if t.ConversationRef == nil { - logging.Logger.Infof("Skipping SendMessage since conversation ref not set") + log.Infof("Skipping SendMessage since conversation ref not set") return nil } err := t.Adapter.ProactiveMessage(context.TODO(), *t.ConversationRef, coreActivity.HandlerFuncs{ @@ -290,20 +306,20 @@ func (t *Teams) SendMessage(msg string) error { if err != nil { return err } - logging.Logger.Debug("Message successfully sent to MS Teams") + log.Debug("Message successfully sent to MS Teams") return nil } func (t *Teams) sendProactiveMessage(card map[string]interface{}) error { if t.ConversationRef == nil { - logging.Logger.Infof("Skipping SendMessage since conversation ref not set") + log.Infof("Skipping SendMessage since conversation ref not set") return nil } err := t.Adapter.ProactiveMessage(context.TODO(), *t.ConversationRef, coreActivity.HandlerFuncs{ OnMessageFunc: func(turn *coreActivity.TurnContext) (schema.Activity, error) { attachments := []schema.Attachment{ { - ContentType: "application/vnd.microsoft.card.adaptive", + ContentType: contentTypeCard, Content: card, }, } diff --git a/pkg/bot/teams_notif.go b/pkg/bot/teams_notif.go index 499d057ec..48476ca89 100644 --- a/pkg/bot/teams_notif.go +++ b/pkg/bot/teams_notif.go @@ -14,12 +14,12 @@ const ( schemaContext = "http://schema.org/extensions" ) -var themeColor = map[events.Level]string{ - events.Info: "good", - events.Warn: "warning", - events.Debug: "good", - events.Error: "attention", - events.Critical: "attention", +var themeColor = map[config.Level]string{ + config.Info: "good", + config.Warn: "warning", + config.Debug: "good", + config.Error: "attention", + config.Critical: "attention", } type Fact map[string]interface{} diff --git a/pkg/config/config.go b/pkg/config/config.go index 0bbf40d55..51d973d63 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -59,6 +59,13 @@ const ( Error Level = "error" // Critical level Critical Level = "critical" + + // Slack bot platform + SlackBot BotPlatform = "slack" + // MattermostBot bot platform + MattermostBot BotPlatform = "mattermost" + // TeamsBot bot platform + TeamsBot BotPlatform = "teams" ) // EventType to watch @@ -67,6 +74,9 @@ type EventType string // Level type to store event levels type Level string +// BotPlatform supported by BotKube +type BotPlatform string + // ResourceConfigFileName is a name of botkube resource configuration file var ResourceConfigFileName = "resource_config.yaml" @@ -175,7 +185,7 @@ type Teams struct { AppID string `yaml:"appID,omitempty"` AppPassword string `yaml:"appPassword,omitempty"` Team string - Channel string + Port string NotifType NotifType `yaml:",omitempty"` } diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index e03dfa10d..798e4cfbc 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -45,7 +45,7 @@ import ( const ( controllerStartMsg = "...and now my watch begins for cluster '%s'! :crossed_swords:" - controllerStopMsg = "my watch has ended for cluster '%s'!" + controllerStopMsg = "My watch has ended for cluster '%s'!\nPlease send `@BotKube notifier start` to enable notification once BotKube comes online." configUpdateMsg = "Looks like the configuration is updated for cluster '%s'. I shall halt my watch till I read it." ) @@ -109,10 +109,12 @@ func RegisterInformers(c *config.Config, notifiers []notify.Notifier) { utils.KubeInformerFactory.Start(stopCh) sigterm := make(chan os.Signal, 1) - signal.Notify(sigterm, syscall.SIGTERM) - signal.Notify(sigterm, syscall.SIGINT) + signal.Notify(sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGSTOP) + <-sigterm sendMessage(c, notifiers, fmt.Sprintf(controllerStopMsg, c.Settings.ClusterName)) + // Sleep for some time to send termination notification + time.Sleep(5 * time.Second) } func registerEventHandlers(c *config.Config, notifiers []notify.Notifier, resourceType string, events []config.EventType) (handlerFns cache.ResourceEventHandlerFuncs) { diff --git a/pkg/execute/executor.go b/pkg/execute/executor.go index d8771a91d..55b5937da 100644 --- a/pkg/execute/executor.go +++ b/pkg/execute/executor.go @@ -73,6 +73,10 @@ const ( filterNameMissing = "You forgot to pass filter name. Please pass one of the following valid filters:\n\n%s" filterEnabled = "I have enabled '%s' filter on '%s' cluster." filterDisabled = "Done. I won't run '%s' filter on '%s' cluster." + + // Custom messages for teams platform + teamsUnsupportedCmdMsg = "Command not supported. Please visit botkube.io/usage to see supported commands." + teamsIncompleteCmdMsg = "You missed to pass options for the command. Please run /botkubehelp to see command options." ) // Executor is an interface for processes to execute commands @@ -82,6 +86,7 @@ type Executor interface { // DefaultExecutor is a default implementations of Executor type DefaultExecutor struct { + Platform config.BotPlatform Message string AllowKubectl bool RestrictAccess bool @@ -143,8 +148,9 @@ func (action FiltersAction) String() string { // NewDefaultExecutor returns new Executor object func NewDefaultExecutor(msg string, allowkubectl, restrictAccess bool, defaultNamespace, - clusterName, channelName string, isAuthChannel bool) Executor { + clusterName string, platform config.BotPlatform, channelName string, isAuthChannel bool) Executor { return &DefaultExecutor{ + Platform: platform, Message: msg, AllowKubectl: allowkubectl, RestrictAccess: restrictAccess, @@ -179,7 +185,7 @@ func (e *DefaultExecutor) Execute() string { } } if ValidNotifierCommand[args[0]] { - return runNotifierCommand(args, e.ClusterName, e.IsAuthChannel) + return e.runNotifierCommand(args, e.ClusterName, e.IsAuthChannel) } if validPingCommand[args[0]] { res := runVersionCommand(args, e.ClusterName) @@ -193,15 +199,18 @@ func (e *DefaultExecutor) Execute() string { } // Check if filter command if validFilterCommand[args[0]] { - return runFilterCommand(args, e.ClusterName, e.IsAuthChannel) + return e.runFilterCommand(args, e.ClusterName, e.IsAuthChannel) } if e.IsAuthChannel { - return unsupportedCmdMsg + return printDefaultMsg(e.Platform) } return "" } -func printDefaultMsg() string { +func printDefaultMsg(p config.BotPlatform) string { + if p == config.TeamsBot { + return teamsUnsupportedCmdMsg + } return unsupportedCmdMsg } @@ -270,7 +279,7 @@ func runKubectlCommand(args []string, clusterName, defaultNamespace string, isAu } // TODO: Have a seperate cli which runs bot commands -func runNotifierCommand(args []string, clusterName string, isAuthChannel bool) string { +func (e *DefaultExecutor) runNotifierCommand(args []string, clusterName string, isAuthChannel bool) string { if isAuthChannel == false { return "" } @@ -281,7 +290,7 @@ func runNotifierCommand(args []string, clusterName string, isAuthChannel bool) s switch args[1] { case Start.String(): config.Notify = true - log.Logger.Info("Notifier enabled") + log.Info("Notifier enabled") return fmt.Sprintf(NotifierStartMsg, clusterName) case Stop.String(): config.Notify = false @@ -300,11 +309,11 @@ func runNotifierCommand(args []string, clusterName string, isAuthChannel bool) s } return fmt.Sprintf("Showing config for cluster '%s'\n\n%s", clusterName, out) } - return printDefaultMsg() + return printDefaultMsg(e.Platform) } // runFilterCommand to list, enable or disable filters -func runFilterCommand(args []string, clusterName string, isAuthChannel bool) string { +func (e *DefaultExecutor) runFilterCommand(args []string, clusterName string, isAuthChannel bool) string { if isAuthChannel == false { return "" } @@ -339,7 +348,7 @@ func runFilterCommand(args []string, clusterName string, isAuthChannel bool) str } return fmt.Sprintf(filterDisabled, args[2], clusterName) } - return printDefaultMsg() + return printDefaultMsg(e.Platform) } // Use tabwriter to display string in tabular form From f2869a735131248fe45033488f7e2d68a017bec8 Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Sun, 26 Jul 2020 17:45:15 +0530 Subject: [PATCH 06/10] Update helm charts to create ingress and svc Signed-off-by: Prasad Ghangal --- cmd/botkube/main.go | 1 - comm_config.yaml | 8 +++---- deploy-all-in-one-tls.yaml | 8 +++++++ deploy-all-in-one.yaml | 7 +++--- helm/botkube/templates/ingress.yaml | 33 +++++++++++++++++++++++++++++ helm/botkube/templates/service.yaml | 8 ++++++- helm/botkube/values.yaml | 20 +++++++++++++++++ 7 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 helm/botkube/templates/ingress.yaml diff --git a/cmd/botkube/main.go b/cmd/botkube/main.go index d5cb86193..26e0b6ce4 100644 --- a/cmd/botkube/main.go +++ b/cmd/botkube/main.go @@ -30,7 +30,6 @@ import ( "github.com/infracloudio/botkube/pkg/metrics" "github.com/infracloudio/botkube/pkg/notify" "github.com/infracloudio/botkube/pkg/utils" - _ "k8s.io/client-go/plugin/pkg/client/auth" ) const ( diff --git a/comm_config.yaml b/comm_config.yaml index 88b7bc93b..4e61503dd 100644 --- a/comm_config.yaml +++ b/comm_config.yaml @@ -18,11 +18,11 @@ communications: # Settings for MS Teams teams: - enabled: true - appID: "4300ff5c-ec94-4f13-a0ed-b9bf4a9da064" - appPassword: "XkuXl18Bl6pU-=e7oZRkuDLdbERDzI-/" - port: 3978 + enabled: false + appID: 'APPLICATION_ID' + appPassword: 'APPLICATION_PASSWORD' notiftype: short + port: 3978 # Settings for ELS elasticsearch: diff --git a/deploy-all-in-one-tls.yaml b/deploy-all-in-one-tls.yaml index 7e4eb92ae..a8ab8c347 100644 --- a/deploy-all-in-one-tls.yaml +++ b/deploy-all-in-one-tls.yaml @@ -272,6 +272,14 @@ stringData: type: botkube-event shards: 1 replicas: 0 + + # Settings for MS Teams + teams: + enabled: false + appID: 'APPLICATION_ID' + appPassword: 'APPLICATION_PASSWORD' + notiftype: short + port: 3978 # Settings for Webhook webhook: diff --git a/deploy-all-in-one.yaml b/deploy-all-in-one.yaml index 58994d0bf..df698cdb2 100644 --- a/deploy-all-in-one.yaml +++ b/deploy-all-in-one.yaml @@ -275,10 +275,11 @@ stringData: # Settings for MS Teams teams: - enabled: true - appID: "4300ff5c-ec94-4f13-a0ed-b9bf4a9da064" - appPassword: "XkuXl18Bl6pU-=e7oZRkuDLdbERDzI-/" + enabled: false + appID: 'APPLICATION_ID' + appPassword: 'APPLICATION_PASSWORD' notiftype: short + port: 3978 # Settings for Webhook webhook: diff --git a/helm/botkube/templates/ingress.yaml b/helm/botkube/templates/ingress.yaml new file mode 100644 index 000000000..8eee55b68 --- /dev/null +++ b/helm/botkube/templates/ingress.yaml @@ -0,0 +1,33 @@ +{{ if .Values.ingress.create }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ include "botkube.fullname" . }} + labels: + app.kubernetes.io/name: {{ include "botkube.name" . }} + helm.sh/chart: {{ include "botkube.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app: botkube + annotations: + {{- if .Values.ingress.annotations }} +{{ toYaml .Values.ingress.annotations | indent 4 }} + {{- end }} +spec: +{{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.host }} + secretName: {{ .Values.ingress.tls.secretName }} +{{- end }} + rules: + - http: + paths: + - path: {{ .Values.ingress.urlPath }} + backend: + serviceName: {{ include "botkube.fullname" . }} + servicePort: {{ .Values.communications.teams.port }} + {{- if .Values.ingress.host }} + host: {{ .Values.ingress.host }} + {{- end }} +{{- end -}} diff --git a/helm/botkube/templates/service.yaml b/helm/botkube/templates/service.yaml index 568ff70db..1ec359f5d 100644 --- a/helm/botkube/templates/service.yaml +++ b/helm/botkube/templates/service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.serviceMonitor.enabled }} +{{- if or .Values.serviceMonitor.enabled .Values.communications.teams.enabled }} apiVersion: v1 kind: Service metadata: @@ -12,9 +12,15 @@ metadata: spec: type: ClusterIP ports: + {{- if .Values.serviceMonitor.enabled }} - name: {{ .Values.service.name }} port: {{ .Values.service.port }} targetPort: {{ .Values.service.targetPort }} + {{- end }} + {{- if .Values.communications.teams.enabled }} + - name: "teams" + port: {{ .Values.communications.teams.port }} + {{- end }} selector: app: botkube {{- end }} diff --git a/helm/botkube/values.yaml b/helm/botkube/values.yaml index 9d9833dbb..32e1d5fea 100644 --- a/helm/botkube/values.yaml +++ b/helm/botkube/values.yaml @@ -267,6 +267,14 @@ communications: channel: 'MATTERMOST_CHANNEL' # Mattermost Channel for receiving BotKube alerts notiftype: short # Change notification type short/long you want to receive. notiftype is optional and Default notification type is short (if not specified) + # Settings for MS Teams + teams: + enabled: false + appID: 'APPLICATION_ID' + appPassword: 'APPLICATION_PASSWORD' + notiftype: short + port: 3978 + # Settings for ELS elasticsearch: enabled: false @@ -293,6 +301,18 @@ service: name: metrics port: 2112 targetPort: 2112 + +# Ingress settings to expose teams endpoint +ingress: + create: false + annotations: + kubernetes.io/ingress.class: nginx + host: 'HOST' + urlPath: "/" + tls: + enabled: false + secretName: '' + serviceMonitor: ## If true, a ServiceMonitor CR is created for a botkube From f88d9b946cb5a2edba6928bf69c365e562844204 Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Mon, 27 Jul 2020 10:43:02 +0530 Subject: [PATCH 07/10] Make teams message path configurable Signed-off-by: Prasad Ghangal --- pkg/bot/teams.go | 13 ++++++++----- pkg/config/config.go | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/bot/teams.go b/pkg/bot/teams.go index 1bd62135f..916ae025b 100644 --- a/pkg/bot/teams.go +++ b/pkg/bot/teams.go @@ -38,7 +38,6 @@ import ( ) const ( - defaultMsgPath = "/api/messages" defaultPort = "3978" consentBufferSize = 100 longRespNotice = "Response is too long. Sending last few lines. Please send DM to BotKube to get complete response." @@ -80,15 +79,19 @@ type ConsentContext struct { func NewTeamsBot(c *config.Config) *Teams { // Set notifier off by default config.Notify = false - port := defaultPort - if c.Communications.Teams.Port != "" { - port = c.Communications.Teams.Port + port := c.Communications.Teams.Port + if port == "" { + port = defaultPort + } + msgPath := c.Communications.Teams.MessagePath + if msgPath == "" { + msgPath = "/" } return &Teams{ AppID: c.Communications.Teams.AppID, AppPassword: c.Communications.Teams.AppPassword, NotifType: c.Communications.Teams.NotifType, - MessagePath: defaultMsgPath, + MessagePath: msgPath, Port: port, AllowKubectl: c.Settings.Kubectl.Enabled, RestrictAccess: c.Settings.Kubectl.RestrictAccess, diff --git a/pkg/config/config.go b/pkg/config/config.go index 51d973d63..4e7e1b46b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -186,6 +186,7 @@ type Teams struct { AppPassword string `yaml:"appPassword,omitempty"` Team string Port string + MessagePath string NotifType NotifType `yaml:",omitempty"` } From 68612a15751fa8fa849caa0bd78597ac4db7b542 Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Mon, 27 Jul 2020 10:43:11 +0530 Subject: [PATCH 08/10] Update deps Signed-off-by: Prasad Ghangal --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 833607026..3dc2e0c9c 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/gorilla/websocket v1.4.1 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/imdario/mergo v0.3.7 // indirect - github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf + github.com/infracloudio/msbotbuilder-go v0.2.1 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/lib/pq v1.2.0 // indirect github.com/mattermost/gorp v2.0.0+incompatible // indirect diff --git a/go.sum b/go.sum index 6207acd0e..5017cb985 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf h1:oRSRni/lJYTELBzNprJAn291ZkpjbbKAMKU4OJV4blg= -github.com/infracloudio/msbotbuilder-go v0.2.1-0.20200411121620-e6b496f9febf/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= +github.com/infracloudio/msbotbuilder-go v0.2.1 h1:bNtsNHwgPXTdD57Uone7FirMPJ1krqudFapCvtIpL+4= +github.com/infracloudio/msbotbuilder-go v0.2.1/go.mod h1:zTFZH9V4x9YQMXrBw2CNsI6hO6blIQ8jHNvdnjbAqZM= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= From bff29ce8ce97e6baad5e007f85876e28db4e9938 Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Mon, 3 Aug 2020 10:42:19 +0530 Subject: [PATCH 09/10] Fix golint issues Signed-off-by: Prasad Ghangal --- pkg/bot/teams.go | 5 +++-- pkg/bot/teams_notif.go | 21 ++++++++++----------- pkg/config/config.go | 2 +- pkg/execute/executor.go | 8 ++++++-- pkg/notify/slack.go | 1 + 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pkg/bot/teams.go b/pkg/bot/teams.go index 916ae025b..8cc08cfa1 100644 --- a/pkg/bot/teams.go +++ b/pkg/bot/teams.go @@ -71,7 +71,7 @@ type Teams struct { ConversationRef *schema.ConversationReference } -type ConsentContext struct { +type consentContext struct { Command string } @@ -189,7 +189,7 @@ func (t *Teams) processActivity(w http.ResponseWriter, req *http.Request) { } // Parse context - consentCtx := ConsentContext{} + consentCtx := consentContext{} ctxJSON, err := json.Marshal(turn.Activity.Value["context"]) if err != nil { return schema.Activity{}, err @@ -286,6 +286,7 @@ func (t *Teams) putRequest(u string, data []byte) error { return nil } +// SendEvent sends event message via Bot interface func (t *Teams) SendEvent(event events.Event) error { card := formatTeamsMessage(event, t.NotifType) if err := t.sendProactiveMessage(card); err != nil { diff --git a/pkg/bot/teams_notif.go b/pkg/bot/teams_notif.go index 48476ca89..c4de7013d 100644 --- a/pkg/bot/teams_notif.go +++ b/pkg/bot/teams_notif.go @@ -22,7 +22,7 @@ var themeColor = map[config.Level]string{ config.Critical: "attention", } -type Fact map[string]interface{} +type fact map[string]interface{} func formatTeamsMessage(event events.Event, notifType config.NotifType) map[string]interface{} { switch notifType { @@ -35,7 +35,6 @@ func formatTeamsMessage(event events.Event, notifType config.NotifType) map[stri default: return teamsShortNotification(event) } - return nil } func teamsShortNotification(event events.Event) map[string]interface{} { @@ -67,29 +66,29 @@ func teamsLongNotification(event events.Event) map[string]interface{} { "version": "1.0", } - sectionFacts := []Fact{} + sectionFacts := []fact{} if event.Cluster != "" { - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Cluster", "value": event.Cluster, }) } - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Name", "value": event.Name, }) if event.Namespace != "" { - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Namespace", "value": event.Namespace, }) } if event.Reason != "" { - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Reason", "value": event.Reason, }) @@ -100,14 +99,14 @@ func teamsLongNotification(event events.Event) map[string]interface{} { for _, m := range event.Messages { message = message + m + "\n" } - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Message", "value": message, }) } if event.Action != "" { - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Action", "value": event.Action, }) @@ -118,7 +117,7 @@ func teamsLongNotification(event events.Event) map[string]interface{} { for _, r := range event.Recommendations { rec = rec + r + "\n" } - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Recommendations", "value": rec, }) @@ -129,7 +128,7 @@ func teamsLongNotification(event events.Event) map[string]interface{} { for _, w := range event.Warnings { warn = warn + w + "\n" } - sectionFacts = append(sectionFacts, Fact{ + sectionFacts = append(sectionFacts, fact{ "title": "Warnings", "value": warn, }) diff --git a/pkg/config/config.go b/pkg/config/config.go index 4e7e1b46b..7130fb83a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -60,7 +60,7 @@ const ( // Critical level Critical Level = "critical" - // Slack bot platform + // SlackBot bot platform SlackBot BotPlatform = "slack" // MattermostBot bot platform MattermostBot BotPlatform = "mattermost" diff --git a/pkg/execute/executor.go b/pkg/execute/executor.go index 55b5937da..bfb34b4ab 100644 --- a/pkg/execute/executor.go +++ b/pkg/execute/executor.go @@ -37,6 +37,7 @@ import ( ) var ( + // ValidNotifierCommand is a map of valid notifier commands ValidNotifierCommand = map[string]bool{ "notifier": true, } @@ -65,15 +66,18 @@ var ( ) const ( - NotifierStartMsg = "Brace yourselves, notifications are coming from cluster '%s'." notifierStopMsg = "Sure! I won't send you notifications from cluster '%s' anymore." unsupportedCmdMsg = "Command not supported. Please run /botkubehelp to see supported commands." - IncompleteCmdMsg = "You missed to pass options for the command. Please run /botkubehelp to see command options." kubectlDisabledMsg = "Sorry, the admin hasn't given me the permission to execute kubectl command on cluster '%s'." filterNameMissing = "You forgot to pass filter name. Please pass one of the following valid filters:\n\n%s" filterEnabled = "I have enabled '%s' filter on '%s' cluster." filterDisabled = "Done. I won't run '%s' filter on '%s' cluster." + // NotifierStartMsg notifier enabled response message + NotifierStartMsg = "Brace yourselves, notifications are coming from cluster '%s'." + // IncompleteCmdMsg incomplete command response message + IncompleteCmdMsg = "You missed to pass options for the command. Please run /botkubehelp to see command options." + // Custom messages for teams platform teamsUnsupportedCmdMsg = "Command not supported. Please visit botkube.io/usage to see supported commands." teamsIncompleteCmdMsg = "You missed to pass options for the command. Please run /botkubehelp to see command options." diff --git a/pkg/notify/slack.go b/pkg/notify/slack.go index 606e57ed3..eae3f572c 100644 --- a/pkg/notify/slack.go +++ b/pkg/notify/slack.go @@ -217,6 +217,7 @@ func slackShortNotification(event events.Event) slack.Attachment { } } +// FormatShortMessage prepares message in short event format func FormatShortMessage(event events.Event) (msg string) { additionalMsg := "" if len(event.Messages) > 0 { From 1d2cbf51d57165030da6bee0e679f19fd896553d Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Tue, 11 Aug 2020 11:23:28 +0530 Subject: [PATCH 10/10] Use go version 1.13 in go.mod Signed-off-by: Prasad Ghangal --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3dc2e0c9c..4d0e0ddf4 100644 --- a/go.mod +++ b/go.mod @@ -52,4 +52,4 @@ require ( k8s.io/kubectl v0.17.0 ) -go 1.12 +go 1.13