Fix slack bot to use the new socket API.

The bot can now send interactive message for approval requests as well
as react to bot mention event.
The bot will try to update the interactive message when possible.
pull/764/head
Benjamin Moermans 2024-07-31 21:22:05 +02:00
parent a6281726ef
commit e44b27a563
5 changed files with 492 additions and 319 deletions

View File

@ -2,6 +2,10 @@ package slack
import (
"fmt"
"github.com/keel-hq/keel/bot"
log "github.com/sirupsen/logrus"
"strings"
"unicode"
"github.com/keel-hq/keel/types"
"github.com/slack-go/slack"
@ -9,127 +13,164 @@ import (
// Request - request approval
func (b *Bot) RequestApproval(req *types.Approval) error {
return b.postMessage(
"Approval required",
req.Message,
types.LevelSuccess.Color(),
[]slack.AttachmentField{
{
Title: "Approval required!",
Value: req.Message + "\n" + fmt.Sprintf("To vote for change type '%s approve %s' to reject it: '%s reject %s'.", b.name, req.Identifier, b.name, req.Identifier),
Short: false,
},
{
Title: "Votes",
Value: fmt.Sprintf("%d/%d", req.VotesReceived, req.VotesRequired),
Short: true,
},
{
Title: "Delta",
Value: req.Delta(),
Short: true,
},
{
Title: "Identifier",
Value: req.Identifier,
Short: true,
},
{
Title: "Provider",
Value: req.Provider.String(),
Short: true,
},
})
return b.postApprovalMessageBlock(
req.ID,
createBlockMessage("Approval required! :mega:", b.name, req),
)
}
func (b *Bot) ReplyToApproval(approval *types.Approval) error {
var title string
switch approval.Status() {
case types.ApprovalStatusPending:
b.postMessage(
"Vote received",
"All approvals received, thanks for voting!",
types.LevelInfo.Color(),
[]slack.AttachmentField{
{
Title: "vote received!",
Value: "Waiting for remaining votes.",
Short: false,
},
{
Title: "Votes",
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
Short: true,
},
{
Title: "Delta",
Value: approval.Delta(),
Short: true,
},
{
Title: "Identifier",
Value: approval.Identifier,
Short: true,
},
})
title = "Approval required! :mega:"
case types.ApprovalStatusRejected:
b.postMessage(
"Change rejected",
"Change was rejected",
types.LevelWarn.Color(),
[]slack.AttachmentField{
{
Title: "change rejected",
Value: "Change was rejected.",
Short: false,
},
{
Title: "Status",
Value: approval.Status().String(),
Short: true,
},
{
Title: "Votes",
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
Short: true,
},
{
Title: "Delta",
Value: approval.Delta(),
Short: true,
},
{
Title: "Identifier",
Value: approval.Identifier,
Short: true,
},
})
title = "Change rejected! :negative_squared_cross_mark:"
case types.ApprovalStatusApproved:
b.postMessage(
"approval received",
"All approvals received, thanks for voting!",
types.LevelSuccess.Color(),
[]slack.AttachmentField{
{
Title: "update approved!",
Value: "All approvals received, thanks for voting!",
Short: false,
},
{
Title: "Votes",
Value: fmt.Sprintf("%d/%d", approval.VotesReceived, approval.VotesRequired),
Short: true,
},
{
Title: "Delta",
Value: approval.Delta(),
Short: true,
},
{
Title: "Identifier",
Value: approval.Identifier,
Short: true,
},
})
title = "Change approved! :tada:"
}
b.upsertApprovalMessage(approval.ID, createBlockMessage(title, b.name, approval))
return nil
}
func createBlockMessage(title string, botName string, req *types.Approval) slack.Blocks {
if req.Expired() {
title = title + " (Expired)"
}
headerText := slack.NewTextBlockObject(
"plain_text",
title,
true,
false,
)
headerSection := slack.NewHeaderBlock(headerText)
messageSection := slack.NewTextBlockObject(
"mrkdwn",
req.Message,
false,
false,
)
messageBlock := slack.NewSectionBlock(messageSection, nil, nil)
votesField := slack.NewTextBlockObject(
"mrkdwn",
fmt.Sprintf("*Votes:*\n%d/%d", req.VotesReceived, req.VotesRequired),
false,
false,
)
deltaField := slack.NewTextBlockObject(
"mrkdwn",
"*Delta:*\n"+req.Delta(),
false,
false,
)
leftDetailSection := slack.NewSectionBlock(
nil,
[]*slack.TextBlockObject{
votesField,
deltaField,
},
nil,
)
identifierField := slack.NewTextBlockObject(
"mrkdwn",
"*Identifier:*\n"+req.Identifier,
false,
false,
)
providerField := slack.NewTextBlockObject(
"mrkdwn",
"*Provider:*\n"+req.Provider.String(),
false,
false,
)
rightDetailSection := slack.NewSectionBlock(nil, []*slack.TextBlockObject{identifierField, providerField}, nil)
commands := bot.BotEventTextToResponse["help"]
var commandTexts []slack.MixedElement
for i, cmd := range commands {
// -- avoid adding first line in commands which is the title.
if i == 0 {
continue
}
cmd = addBotMentionToCommand(cmd, botName)
commandTexts = append(commandTexts, slack.NewTextBlockObject("mrkdwn", cmd, false, false))
}
commandsBlock := slack.NewContextBlock("", commandTexts...)
header := commands[0]
blocks := []slack.Block{
headerSection,
messageBlock,
leftDetailSection,
rightDetailSection,
slack.NewDividerBlock(),
slack.NewContextBlock("", slack.NewTextBlockObject("mrkdwn", header, false, false)),
commandsBlock,
}
if req.VotesReceived < req.VotesRequired && !req.Expired() && !req.Rejected {
approveButton := slack.NewButtonBlockElement(
bot.ApprovalResponseKeyword,
req.Identifier,
slack.NewTextBlockObject(
"plain_text",
"Approve",
true,
false,
),
)
approveButton.Style = slack.StylePrimary
rejectButton := slack.NewButtonBlockElement(
bot.RejectResponseKeyword,
req.Identifier,
slack.NewTextBlockObject(
"plain_text",
"Reject",
true,
false,
),
)
rejectButton.Style = slack.StyleDanger
actionBlock := slack.NewActionBlock("", approveButton, rejectButton)
blocks = append(
blocks,
slack.NewDividerBlock(),
actionBlock,
)
}
return slack.Blocks{
BlockSet: blocks,
}
}
func addBotMentionToCommand(command string, botName string) string {
// -- retrieve the first letter of the command in order to insert bot mention
firstLetterPos := -1
for i, r := range command {
if unicode.IsLetter(r) {
firstLetterPos = i
break
}
}
if firstLetterPos < 0 {
log.Debugf("Unable to find the first letter of the command '%s', let the command without the bot mention.", command)
return command
}
return strings.Replace(
command[:firstLetterPos]+fmt.Sprintf("@%s ", botName)+command[firstLetterPos:],
"\"",
"`",
-1,
)
}

View File

@ -2,19 +2,16 @@ package slack
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/keel-hq/keel/bot"
"github.com/keel-hq/keel/constants"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
"github.com/slack-go/slack/socketmode"
"os"
"strconv"
"strings"
"time"
"github.com/slack-go/slack"
"github.com/keel-hq/keel/bot"
"github.com/keel-hq/keel/constants"
"github.com/keel-hq/keel/version"
log "github.com/sirupsen/logrus"
)
@ -28,10 +25,13 @@ type Bot struct {
msgPrefix string
slackClient *slack.Client
slackRTM *slack.RTM
slackSocket *socketmode.Client
approvalsChannel string // slack approvals channel name
// the approval channel-name, provided by the bot configuration
approvalsChannel string
// the identifier of the approval channel, this is retrieved when the bot is starting
approvalChannelId string
ctx context.Context
botMessagesChannel chan *bot.BotMessage
@ -43,31 +43,51 @@ func init() {
}
func (b *Bot) Configure(approvalsRespCh chan *bot.ApprovalResponse, botMessagesChannel chan *bot.BotMessage) bool {
if os.Getenv(constants.EnvSlackToken) != "" {
botToken := os.Getenv(constants.EnvSlackBotToken)
b.name = "keel"
if botName := os.Getenv(constants.EnvSlackBotName); botName != "" {
b.name = botName
if !strings.HasPrefix(botToken, "xoxb-") {
log.Infof("bot.slack.Configure(): %s must have the prefix \"xoxb-\", skip bot configuration.", constants.EnvSlackBotToken)
return false
}
token := os.Getenv(constants.EnvSlackToken)
appToken := os.Getenv(constants.EnvSlackAppToken)
if !strings.HasPrefix(appToken, "xapp-") {
log.Infof("bot.slack.Configure(): %s must have the previf \"xapp-\".", constants.EnvSlackAppToken)
return false
}
botName, botNameConfigured := os.LookupEnv(constants.EnvSlackBotName)
if !botNameConfigured {
botName = "keel"
}
b.name = botName
channel, channelConfigured := os.LookupEnv(constants.EnvSlackApprovalsChannel)
if !channelConfigured {
channel = "general"
}
b.approvalsChannel = strings.TrimPrefix(channel, "#")
log.Debugf("Configuring slack with approval channel '%s' and bot '%s'", b.approvalsChannel, b.name)
debug, _ := strconv.ParseBool(os.Getenv("DEBUG"))
client := slack.New(token, slack.OptionDebug(debug))
api := slack.New(
botToken,
slack.OptionDebug(debug),
slack.OptionAppLevelToken(appToken),
)
b.approvalsChannel = "general"
if channel := os.Getenv(constants.EnvSlackApprovalsChannel); channel != "" {
b.approvalsChannel = strings.TrimPrefix(channel, "#")
}
client := socketmode.New(
api,
socketmode.OptionDebug(debug),
)
b.slackClient = client
b.slackSocket = client
b.approvalsRespCh = approvalsRespCh
b.botMessagesChannel = botMessagesChannel
return true
}
log.Info("bot.slack.Configure(): Slack approval bot is not configured")
return false
}
// Start - start bot
@ -75,154 +95,179 @@ func (b *Bot) Start(ctx context.Context) error {
// setting root context
b.ctx = ctx
users, err := b.slackClient.GetUsers()
users, err := b.slackSocket.GetUsers()
if err != nil {
return err
}
b.users = map[string]string{}
// -- retrieve the bot user identifier from the bot name
var foundBots []string
for _, user := range users {
switch user.Name {
case b.name:
if user.IsBot {
foundBots = append(foundBots, user.Name)
if user.Name == b.name {
b.id = user.ID
break
}
default:
continue
}
}
if b.id == "" {
return errors.New("could not find bot in the list of names, check if the bot is called \"" + b.name + "\" ")
return errors.New("could not find bot in the list of names, check if the bot is called \"" + b.name + "\", found bots: " + strings.Join(foundBots[:], ", "))
}
// -- mentions and direct messages start with this message prefix. It is used from trimming the messages
b.msgPrefix = strings.ToLower("<@" + b.id + ">")
go b.startInternal()
// -- retrieve the channel identifier from the approval channel name
b.approvalChannelId, err = b.findChannelId(b.approvalsChannel)
if err != nil {
return err
}
go b.listenForSocketEvents()
return nil
}
func (b *Bot) startInternal() error {
b.slackRTM = b.slackClient.NewRTM()
func (b *Bot) findChannelId(channelName string) (string, error) {
var channelId string
var cursor string
go b.slackRTM.ManageConnection()
// -- while the channel is not found, fetch pages
for channelId == "" {
channels, nextCursor, err := b.slackSocket.GetConversationsForUser(&slack.GetConversationsForUserParameters{ExcludeArchived: true, Cursor: cursor})
if err != nil {
return "", err
}
for msg := range b.slackRTM.IncomingEvents {
switch ev := msg.Data.(type) {
case *slack.HelloEvent:
// Ignore hello
case *slack.ConnectedEvent:
// nothing to do
case *slack.MessageEvent:
b.handleMessage(ev)
case *slack.PresenceChangeEvent:
// nothing to do
case *slack.RTMError:
log.Errorf("Error: %s", ev.Error())
case *slack.InvalidAuthEvent:
log.Error("Invalid credentials")
return fmt.Errorf("invalid credentials")
default:
// Ignore other events..
// fmt.Printf("Unexpected: %v\n", msg.Data)
for _, channel := range channels {
if channel.Name == channelName {
channelId = channel.ID
break
}
}
// -- channel not found on this page, check if there are more pages
if nextCursor == "" {
break
}
// -- continue to the next page
cursor = nextCursor
}
if channelId == "" {
return "", errors.New("Unable to retrieve the channel named \"" + channelName + "\". Check that the bot is invited to that channel and define the proper scope in the Slack app settings.")
} else {
return channelId, nil
}
}
func (b *Bot) listenForSocketEvents() error {
go func() {
for evt := range b.slackSocket.Events {
switch evt.Type {
case socketmode.EventTypeConnecting:
log.Info("Connecting to Slack with Socket Mode...")
case socketmode.EventTypeConnectionError:
if "missing_scope" == evt.Data {
log.Error("The application token is missing scopes, verify to provide an application token with the scope 'connections:write'", evt.Data)
} else {
log.Error("Connection failed. Retrying later... ", evt.Data)
}
case socketmode.EventTypeConnected:
log.Info("Connected to Slack with Socket Mode.")
case socketmode.EventTypeInvalidAuth:
log.Error("Invalid authentication parameter provided.", evt.Data)
case socketmode.EventTypeDisconnect:
log.Info("Disconnected from Slack socket.")
case socketmode.EventTypeIncomingError:
log.Error("An error occurred while processing an incoming event.", evt.Data)
case socketmode.EventTypeErrorBadMessage:
log.Error("Bad message error.", evt.Data)
case socketmode.EventTypeErrorWriteFailed:
log.Error("Error while responding to a message.", evt.Data)
case socketmode.EventTypeSlashCommand:
// ignore slash commands
case socketmode.EventTypeEventsAPI:
// The bot can receive mention events only when the bot has the Event Subscriptions enabled
// AND has a subscription to "app_mention" events
eventsAPIEvent, isEventApiEvent := evt.Data.(slackevents.EventsAPIEvent)
if !isEventApiEvent {
continue
}
innerEvent := eventsAPIEvent.InnerEvent
mentionEvent, isAppMentionEvent := innerEvent.Data.(*slackevents.AppMentionEvent)
if isAppMentionEvent && eventsAPIEvent.Type == slackevents.CallbackEvent {
// -- the bot was mentioned in a message, try to process the command
b.handleMentionEvent(mentionEvent)
b.slackSocket.Ack(*evt.Request)
}
case socketmode.EventTypeInteractive:
callback, isInteractionCallback := evt.Data.(slack.InteractionCallback)
if !isInteractionCallback {
log.Debugf("Ignoring Event %+v\n", evt)
continue
}
if callback.Type == slack.InteractionTypeBlockActions {
if (len(callback.ActionCallback.BlockActions)) == 0 {
log.Error("No block actions found")
continue
}
// callback.ResponseURL
blockAction := callback.ActionCallback.BlockActions[0]
b.handleAction(callback.User.ID, blockAction)
b.slackSocket.Ack(*evt.Request)
}
}
}
}()
b.slackSocket.Run()
return fmt.Errorf("No more events?")
}
func (b *Bot) postMessage(title, message, color string, fields []slack.AttachmentField) error {
params := slack.NewPostMessageParameters()
params.Username = b.name
params.IconURL = b.getBotUserIconURL()
attachments := []slack.Attachment{
{
Fallback: message,
Color: color,
Fields: fields,
Footer: fmt.Sprintf("https://keel.sh %s", version.GetKeelVersion().Version),
Ts: json.Number(strconv.Itoa(int(time.Now().Unix()))),
},
}
var mgsOpts []slack.MsgOption
mgsOpts = append(mgsOpts, slack.MsgOptionPostMessageParameters(params))
mgsOpts = append(mgsOpts, slack.MsgOptionAttachments(attachments...))
_, _, err := b.slackClient.PostMessage(b.approvalsChannel, mgsOpts...)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"approvals_channel": b.approvalsChannel,
}).Error("bot.postMessage: failed to send message")
}
return err
}
// checking if message was received in approvals channel
func (b *Bot) isApprovalsChannel(event *slack.MessageEvent) bool {
channel, err := b.slackClient.GetConversationInfo(&slack.GetConversationInfoInput{ChannelID: event.Channel})
if err != nil {
if channel != (&slack.Channel{}) && channel != nil {
if channel.GroupConversation.Name == b.approvalsChannel &&
channel.GroupConversation.Conversation.IsPrivate {
return true
} else {
log.Errorf("couldn't find amongst private conversations: %s", err)
}
}
log.WithError(err).Errorf("channel with ID %s could not be retrieved", event.Channel)
return false
}
log.Debugf("checking if approvals channel: %s==%s", channel.GroupConversation.Name, b.approvalsChannel)
if channel.GroupConversation.Name == b.approvalsChannel {
return true
}
log.Debugf("message was received not on approvals channel (%s)", channel.GroupConversation.Name)
return false
}
func (b *Bot) handleMessage(event *slack.MessageEvent) {
if event.BotID != "" || event.User == "" || event.SubType == "bot_message" {
// handleMentionEvent - Handle a mention event. The bot will only receive its own mention event. No need to check that the message is for him.
func (b *Bot) handleMentionEvent(event *slackevents.AppMentionEvent) {
if event.BotID != "" || event.User == "" {
log.WithFields(log.Fields{
"event_bot_ID": event.BotID,
"event_user": event.User,
"msg": event.Text,
"event_subtype": event.SubType,
}).Debug("handleMessage: ignoring message")
return
}
// -- clean the text message to have only the action (approve or reject) followed by the resource identifier
// -- (e.g. approve k8s/project/repo:1.2.3)
eventText := strings.Trim(strings.ToLower(event.Text), " \n\r")
if !b.isBotMessage(event, eventText) {
return
}
eventText = b.trimBotName(eventText)
eventText = b.trimBot(eventText)
// -- first, try to handle the message as an approval response
approval, isAnApprovalResponse := bot.IsApproval(event.User, eventText)
approval, ok := bot.IsApproval(event.User, eventText)
// only accepting approvals from approvals channel
if ok && b.isApprovalsChannel(event) {
if isAnApprovalResponse && b.isEventFromApprovalsChannel(event) {
// -- the message is processed by bot\approvals.go in ProcessApprovalResponses
b.approvalsRespCh <- approval
return
} else if ok {
} else if isAnApprovalResponse {
log.WithFields(log.Fields{
"received_on": event.Channel,
"approvals_chan": b.approvalsChannel,
}).Warnf("message was received not in approvals channel: %s", event.Channel)
b.Respond(fmt.Sprintf("please use approvals channel '%s'", b.approvalsChannel), event.Channel)
}).Warnf("The message was not received in the approval channel: %s", event.Channel)
b.Respond(fmt.Sprintf("Please use approvals channel '%s'", b.approvalsChannel), event.Channel)
return
}
// -- the message is not an approval response, try to handle the message as a generic bot command
b.botMessagesChannel <- &bot.BotMessage{
Message: eventText,
User: event.User,
@ -231,49 +276,7 @@ func (b *Bot) handleMessage(event *slack.MessageEvent) {
}
}
func (b *Bot) Respond(text string, channel string) {
// if message is short, replying directly via slack RTM
if len(text) < 3000 {
b.slackRTM.SendMessage(b.slackRTM.NewOutgoingMessage(formatAsSnippet(text), channel))
return
}
// longer messages are getting uploaded as files
f := slack.FileUploadParameters{
Filename: "keel response",
Content: text,
Filetype: "text",
Channels: []string{channel},
}
_, err := b.slackClient.UploadFile(f)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Respond: failed to send message")
}
}
func (b *Bot) isBotMessage(event *slack.MessageEvent, eventText string) bool {
prefixes := []string{
b.msgPrefix,
b.name,
// "kel",
}
for _, p := range prefixes {
if strings.HasPrefix(eventText, p) {
return true
}
}
// Direct message channels always starts with 'D'
return strings.HasPrefix(event.Channel, "D")
}
func (b *Bot) trimBot(msg string) string {
func (b *Bot) trimBotName(msg string) string {
msg = strings.Replace(msg, strings.ToLower(b.msgPrefix), "", 1)
msg = strings.TrimPrefix(msg, b.name)
msg = strings.Trim(msg, " :\n")
@ -281,18 +284,146 @@ func (b *Bot) trimBot(msg string) string {
return msg
}
func formatAsSnippet(response string) string {
return "```" + response + "```"
// isEventFromApprovalsChannel - checking if message was received in approvals channel
func (b *Bot) isEventFromApprovalsChannel(event *slackevents.AppMentionEvent) bool {
if b.approvalChannelId == event.Channel {
return true
} else {
log.Debug("Message was not received on the approvals channel, ignoring")
return false
}
}
func (b *Bot) getBotUserIconURL() string {
res, err := b.slackClient.GetUserInfo(b.id)
// handleAction - Handle an action performed by using the slack block action feature.
// The bot will only receive events coming from its own action blocks. Block action can only be used to approve
// or reject an approval request (other commands should be managed by user bot mentions).
func (b *Bot) handleAction(username string, blockAction *slack.BlockAction) {
eventText := fmt.Sprintf("%s %s", blockAction.ActionID, blockAction.Value)
approval, ok := bot.IsApproval(username, eventText)
if !ok {
// -- only react to approval requests (approve or reject actions)
log.WithFields(log.Fields{
"action_user": username,
"action_id": blockAction.ActionID,
"action_value": blockAction.Value,
}).Debug("handleAction: ignoring action, clicked on unknown button")
return
}
b.approvalsRespCh <- approval
}
// postApprovalMessageBlock - effectively post a message to the approval channel
func (b *Bot) postApprovalMessageBlock(approvalId string, blocks slack.Blocks) error {
channelID := b.approvalsChannel
_, _, err := b.slackSocket.PostMessage(
channelID,
slack.MsgOptionBlocks(blocks.BlockSet...),
createApprovalMetadata(approvalId),
)
return err
}
// Respond - This method sent the text message to the provided channel
func (b *Bot) Respond(text string, channel string) {
// if message is short, replying directly via socket
if len(text) < 3000 {
b.slackSocket.SendMessage(channel, slack.MsgOptionText(formatAsSnippet(text), true))
return
}
// longer messages are getting uploaded as files
f := slack.FileUploadParameters{
Filename: "keel response",
Content: text,
Filetype: "text",
Channels: []string{channel},
}
_, err := b.slackSocket.UploadFile(f)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"bot_id": b.id,
}).Error("bot.postMessage: failed to retrieve bot user icon url")
}).Error("Respond: failed to send message")
}
}
// upsertApprovalMessage - update the approval message that was sent for the given resource identifier (deployment/default/wd:0.0.15).
// if the message is not found in the approval channel it will be created. That way even it the message is deleted,
// we will see the approval status
func (b *Bot) upsertApprovalMessage(approvalId string, blocks slack.Blocks) {
// Retrieve the message history
historyParams := &slack.GetConversationHistoryParameters{
ChannelID: b.approvalChannelId,
Limit: 250,
IncludeAllMetadata: true,
}
return res.Profile.ImageOriginal
history, err := b.slackSocket.GetConversationHistory(historyParams)
if err != nil {
log.Debugf("Unable to get the conversation history to edit the message, post new one: %v", err)
b.postApprovalMessageBlock(approvalId, blocks)
}
// Find the message to update; the channel id and the message timestamp is the identifier of a message for slack
var messageTs string
for _, message := range history.Messages {
if isMessageOfApprovalRequest(message, approvalId) {
messageTs = message.Timestamp
break // Found the message
}
}
if messageTs == "" {
log.Debug("Unable to find the approval message for the identifier. Post a new message instead")
b.postApprovalMessageBlock(approvalId, blocks)
return
} else {
b.slackSocket.UpdateMessage(
b.approvalChannelId,
messageTs,
slack.MsgOptionBlocks(blocks.BlockSet...),
slack.MsgOptionAsUser(true),
createApprovalMetadata(approvalId),
)
}
}
// isMessageOfApprovalRequest - Check whether the given message is the approval message sent for the given approval identifier.
// Helps to identify the interactive message corresponding to the approval request in order to update the latest status of the approval.
// returns true if it is, false otherwise.
func isMessageOfApprovalRequest(message slack.Message, approvalId string) bool {
if message.Metadata.EventType != "approval" {
return false
}
if message.Metadata.EventPayload == nil {
return false
}
approvalID, ok := message.Metadata.EventPayload["approval_id"].(string)
if !ok {
return false
}
return approvalID == approvalId
}
// createApprovalMetadata - create message metadata, the metadata includes the approval identifier.
// That way, it is possible to identify clearly the approval message for a given approval request when looking into the
// history.
func createApprovalMetadata(approvalId string) slack.MsgOption {
return slack.MsgOptionMetadata(
slack.SlackMetadata{
EventType: "approval",
EventPayload: map[string]interface{}{
"approval_id": approvalId,
},
},
)
}
func formatAsSnippet(response string) string {
return "```" + response + "```"
}

View File

@ -108,12 +108,12 @@ func newTestingUtils() (*sql.SQLStore, func()) {
func TestBotRequest(t *testing.T) {
os.Setenv(constants.EnvSlackToken, "")
os.Setenv(constants.EnvSlackBotToken, "")
f8s := &testutil.FakeK8sImplementer{}
fi := &fakeSlackImplementer{}
token := os.Getenv(constants.EnvSlackToken)
token := os.Getenv(constants.EnvSlackBotToken)
if token == "" {
t.Skip()
}
@ -155,12 +155,12 @@ func TestBotRequest(t *testing.T) {
func TestProcessApprovedResponse(t *testing.T) {
os.Setenv(constants.EnvSlackToken, "")
os.Setenv(constants.EnvSlackBotToken, "")
f8s := &testutil.FakeK8sImplementer{}
fi := &fakeSlackImplementer{}
token := os.Getenv(constants.EnvSlackToken)
token := os.Getenv(constants.EnvSlackBotToken)
if token == "" {
t.Skip()
}
@ -202,12 +202,12 @@ func TestProcessApprovedResponse(t *testing.T) {
func TestProcessApprovalReply(t *testing.T) {
os.Setenv(constants.EnvSlackToken, "")
os.Setenv(constants.EnvSlackBotToken, "")
f8s := &testutil.FakeK8sImplementer{}
fi := &fakeSlackImplementer{}
token := os.Getenv(constants.EnvSlackToken)
token := os.Getenv(constants.EnvSlackBotToken)
if token == "" {
t.Skip()
}
@ -273,12 +273,12 @@ func TestProcessApprovalReply(t *testing.T) {
func TestProcessRejectedReply(t *testing.T) {
os.Setenv(constants.EnvSlackToken, "")
os.Setenv(constants.EnvSlackBotToken, "")
f8s := &testutil.FakeK8sImplementer{}
fi := &fakeSlackImplementer{}
token := os.Getenv(constants.EnvSlackToken)
token := os.Getenv(constants.EnvSlackBotToken)
if token == "" {
t.Skip()
}

View File

@ -11,7 +11,8 @@ const WebhookEndpointEnv = "WEBHOOK_ENDPOINT"
// slack bot/token
const (
EnvSlackToken = "SLACK_TOKEN"
EnvSlackBotToken = "SLACK_BOT_TOKEN"
EnvSlackAppToken = "SLACK_APP_TOKEN"
EnvSlackBotName = "SLACK_BOT_NAME"
EnvSlackChannels = "SLACK_CHANNELS"
EnvSlackApprovalsChannel = "SLACK_APPROVALS_CHANNEL"

View File

@ -33,8 +33,8 @@ func init() {
func (s *sender) Configure(config *notification.Config) (bool, error) {
var token string
// Get configuration
if os.Getenv(constants.EnvSlackToken) != "" {
token = os.Getenv(constants.EnvSlackToken)
if os.Getenv(constants.EnvSlackBotToken) != "" {
token = os.Getenv(constants.EnvSlackBotToken)
} else {
return false, nil
}