mirror of
https://github.com/valitydev/botkube.git
synced 2024-11-06 08:25:19 +00:00
Add MS Teams support (#242)
##### ISSUE TYPE <!--- Pick one below and delete the rest: --> - Feature Pull Request ##### SUMMARY - Add support for Microsoft Teams - Multicluster support not available yet for Teams Integration tests will be addressed with a separate issue. Blocked by https://github.com/infracloudio/msbotbuilder-go/issues/46 Fixes #60
This commit is contained in:
parent
5e3ffc865b
commit
c6db9526a3
@ -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
|
||||
|
@ -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.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")
|
||||
|
@ -15,6 +15,14 @@ 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: 'APPLICATION_ID'
|
||||
appPassword: 'APPLICATION_PASSWORD'
|
||||
notiftype: short
|
||||
port: 3978
|
||||
|
||||
# Settings for ELS
|
||||
elasticsearch:
|
||||
|
@ -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:
|
||||
|
@ -272,7 +272,15 @@ 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:
|
||||
enabled: false
|
||||
|
3
go.mod
3
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.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
|
||||
@ -44,7 +45,7 @@ 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
|
||||
|
16
go.sum
16
go.sum
@ -1,14 +1,21 @@
|
||||
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=
|
||||
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=
|
||||
@ -52,6 +59,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=
|
||||
@ -152,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=
|
||||
@ -170,6 +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 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=
|
||||
@ -198,6 +209,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 +270,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 +446,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=
|
||||
|
33
helm/botkube/templates/ingress.yaml
Normal file
33
helm/botkube/templates/ingress.yaml
Normal file
@ -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 -}}
|
@ -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 }}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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{
|
||||
|
334
pkg/bot/teams.go
Normal file
334
pkg/bot/teams.go
Normal file
@ -0,0 +1,334 @@
|
||||
// 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"
|
||||
"github.com/infracloudio/botkube/pkg/events"
|
||||
"github.com/infracloudio/botkube/pkg/execute"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
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
|
||||
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
|
||||
DefaultNamespace string
|
||||
|
||||
ConversationRef *schema.ConversationReference
|
||||
}
|
||||
|
||||
type consentContext struct {
|
||||
Command string
|
||||
}
|
||||
|
||||
// NewTeamsBot returns Teams instance
|
||||
func NewTeamsBot(c *config.Config) *Teams {
|
||||
// Set notifier off by default
|
||||
config.Notify = false
|
||||
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: msgPath,
|
||||
Port: port,
|
||||
AllowKubectl: c.Settings.Kubectl.Enabled,
|
||||
RestrictAccess: c.Settings.Kubectl.RestrictAccess,
|
||||
DefaultNamespace: c.Settings.Kubectl.DefaultNamespace,
|
||||
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 {
|
||||
log.Errorf("Failed Start teams bot. %+v", err)
|
||||
return
|
||||
}
|
||||
// Start consent cleanup
|
||||
http.HandleFunc(t.MessagePath, t.processActivity)
|
||||
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) {
|
||||
log.Debugf("Deleting activity %s\n", ID)
|
||||
if err := t.Adapter.DeleteActivity(context.Background(), ID, convRef); err != nil {
|
||||
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 {
|
||||
log.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) {
|
||||
resp := t.processMessage(turn.Activity)
|
||||
if len(resp) >= maxMessageSize {
|
||||
if turn.Activity.Conversation.ConversationType == convTypePersonal {
|
||||
// send file upload request
|
||||
attachments := []schema.Attachment{
|
||||
{
|
||||
ContentType: contentTypeFile,
|
||||
Name: responseFileName,
|
||||
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.deleteConsent(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"] != activityFileUpload {
|
||||
return schema.Activity{}, nil
|
||||
}
|
||||
if turn.Activity.Value["action"] != activityAccept {
|
||||
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[activityUploadInfo])
|
||||
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), "<at>BotKube</at>"))
|
||||
e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.DefaultNamespace,
|
||||
t.ClusterName, config.TeamsBot, "", true)
|
||||
out := e.Execute()
|
||||
|
||||
actJSON, _ := json.MarshalIndent(turn.Activity, "", " ")
|
||||
log.Debugf("Incoming MSTeams Activity: %s", actJSON)
|
||||
|
||||
// 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: contentTypeFile,
|
||||
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 {
|
||||
log.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), "<at>BotKube</at>"))
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Multicluster is not supported for Teams
|
||||
e := execute.NewDefaultExecutor(msg, t.AllowKubectl, t.RestrictAccess, t.DefaultNamespace,
|
||||
t.ClusterName, config.TeamsBot, "", true)
|
||||
return fmt.Sprintf("```\n%s\n```", e.Execute())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
log.Errorf("Failed to send notification. %s", err.Error())
|
||||
}
|
||||
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 {
|
||||
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) {
|
||||
return turn.SendActivity(coreActivity.MsgOptionText(msg))
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("Message successfully sent to MS Teams")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Teams) sendProactiveMessage(card map[string]interface{}) error {
|
||||
if t.ConversationRef == nil {
|
||||
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: contentTypeCard,
|
||||
Content: card,
|
||||
},
|
||||
}
|
||||
return turn.SendActivity(coreActivity.MsgOptionAttachments(attachments))
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
150
pkg/bot/teams_notif.go
Normal file
150
pkg/bot/teams_notif.go
Normal file
@ -0,0 +1,150 @@
|
||||
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[config.Level]string{
|
||||
config.Info: "good",
|
||||
config.Warn: "warning",
|
||||
config.Debug: "good",
|
||||
config.Error: "attention",
|
||||
config.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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -59,6 +59,13 @@ const (
|
||||
Error Level = "error"
|
||||
// Critical level
|
||||
Critical Level = "critical"
|
||||
|
||||
// SlackBot 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"
|
||||
|
||||
@ -120,9 +130,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 +179,17 @@ 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
|
||||
Port string
|
||||
MessagePath string
|
||||
NotifType NotifType `yaml:",omitempty"`
|
||||
}
|
||||
|
||||
// Webhook configuration to send notifications
|
||||
type Webhook struct {
|
||||
Enabled bool
|
||||
|
@ -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) {
|
||||
|
@ -37,7 +37,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
validNotifierCommand = map[string]bool{
|
||||
// ValidNotifierCommand is a map of valid notifier commands
|
||||
ValidNotifierCommand = map[string]bool{
|
||||
"notifier": true,
|
||||
}
|
||||
validPingCommand = map[string]bool{
|
||||
@ -65,14 +66,21 @@ 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."
|
||||
)
|
||||
|
||||
// Executor is an interface for processes to execute commands
|
||||
@ -82,6 +90,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 +152,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,
|
||||
@ -178,8 +188,8 @@ func (e *DefaultExecutor) Execute() string {
|
||||
return runKubectlCommand(args, e.ClusterName, e.DefaultNamespace, e.IsAuthChannel)
|
||||
}
|
||||
}
|
||||
if validNotifierCommand[args[0]] {
|
||||
return runNotifierCommand(args, e.ClusterName, e.IsAuthChannel)
|
||||
if ValidNotifierCommand[args[0]] {
|
||||
return e.runNotifierCommand(args, e.ClusterName, e.IsAuthChannel)
|
||||
}
|
||||
if validPingCommand[args[0]] {
|
||||
res := runVersionCommand(args, e.ClusterName)
|
||||
@ -193,15 +203,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,19 +283,19 @@ 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 ""
|
||||
}
|
||||
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)
|
||||
return fmt.Sprintf(NotifierStartMsg, clusterName)
|
||||
case Stop.String():
|
||||
config.Notify = false
|
||||
log.Info("Notifier disabled")
|
||||
@ -300,16 +313,16 @@ 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 ""
|
||||
}
|
||||
if len(args) < 2 {
|
||||
return incompleteCmdMsg
|
||||
return IncompleteCmdMsg
|
||||
}
|
||||
|
||||
switch args[1] {
|
||||
@ -339,7 +352,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
|
||||
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -210,14 +210,15 @@ 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) {
|
||||
// FormatShortMessage prepares message in short event format
|
||||
func FormatShortMessage(event events.Event) (msg string) {
|
||||
additionalMsg := ""
|
||||
if len(event.Messages) > 0 {
|
||||
for _, m := range event.Messages {
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user