Skip to content

Commit 6dba9d0

Browse files
authored
feat(#36): post to multiple slack channels (#38)
* feat(#36): post to multiple slack channels * fix: restore deleted log message * fix: adjusting log message * fix: address review comment * refactor: join err on channel directly * refactor: undo channel business * refactor: move summary and thread msgs out of goroutine
1 parent 019f00b commit 6dba9d0

File tree

3 files changed

+66
-23
lines changed

3 files changed

+66
-23
lines changed

internal/patrol/patrol.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,10 @@ func (s *sheriffService) Patrol(args config.PatrolConfig) (warn error, err error
7474

7575
if s.slackService != nil {
7676
if len(args.ReportToSlackChannels) > 0 {
77-
// TODO #36 support sending to multiple slack channels (for now only the first is sent)
78-
log.Info().Str("slackChannel", args.ReportToSlackChannels[0]).Msg("Posting report to slack channel")
79-
77+
log.Info().Strs("slackChannels", args.ReportToSlackChannels).Msg("Posting report to slack channels")
8078
paths := pie.Map(args.Locations, func(v config.ProjectLocation) string { return v.Path })
81-
if err := publish.PublishAsGeneralSlackMessage(args.ReportToSlackChannels[0], scanReports, paths, s.slackService); err != nil {
82-
log.Error().Err(err).Msg("Failed to post slack report")
79+
if err := publish.PublishAsGeneralSlackMessage(args.ReportToSlackChannels, scanReports, paths, s.slackService); err != nil {
80+
log.Error().Err(err).Msg("Failed to post slack report to some channels")
8381
err = errors.Join(errors.New("failed to post slack report"), err)
8482
warn = errors.Join(err, warn)
8583
}

internal/publish/to_slack.go

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,34 @@ import (
1515
goslack "github.com/slack-go/slack"
1616
)
1717

18-
// PublishAsGeneralSlackMessage publishes a report of the vulnerabilities scanned to a slack channel
19-
func PublishAsGeneralSlackMessage(channelName string, reports []scanner.Report, paths []string, s slack.IService) (err error) {
18+
// PublishAsGeneralSlackMessage publishes a report of the vulnerabilities scanned to a list of slack channels
19+
func PublishAsGeneralSlackMessage(channelNames []string, reports []scanner.Report, paths []string, s slack.IService) error {
20+
var wg sync.WaitGroup
21+
errChan := make(chan error, len(channelNames))
2022
vulnerableReportsBySeverityKind := groupVulnReportsByMaxSeverityKind(reports)
2123

2224
summary := formatSummary(vulnerableReportsBySeverityKind, len(reports), paths)
23-
24-
ts, err := s.PostMessage(channelName, summary...)
25-
if err != nil {
26-
return errors.Join(errors.New("failed to post slack summary"), err)
25+
threadMsgs := formatReportMessage(vulnerableReportsBySeverityKind)
26+
for _, slackChannel := range channelNames {
27+
log.Info().Str("slackChannel", slackChannel).Msg("Posting report to slack channel")
28+
wg.Add(1)
29+
go func(errChan chan<- error) {
30+
defer wg.Done()
31+
if err := publishAsGeneralSlackMessageSingleChannel(slackChannel, summary, threadMsgs, s); err != nil {
32+
log.Error().Err(err).Str("slackChannel", slackChannel).Msg("Failed to post slack report")
33+
errChan <- err
34+
}
35+
}(errChan)
2736
}
37+
wg.Wait()
38+
close(errChan)
2839

29-
msgOptions := formatReportMessage(vulnerableReportsBySeverityKind)
30-
for _, option := range msgOptions {
31-
_, err = s.PostMessage(
32-
channelName,
33-
option,
34-
goslack.MsgOptionTS(ts), // Replies to the summary message in thread
35-
)
36-
if err != nil {
37-
return errors.Join(errors.New("failed to message in slack summary thread"), err)
38-
}
40+
var outErr error
41+
for err := range errChan {
42+
outErr = errors.Join(err, outErr)
3943
}
4044

41-
return
45+
return outErr
4246
}
4347

4448
func PublishAsSpecificChannelSlackMessage(reports []scanner.Report, s slack.IService) (warn error) {
@@ -209,6 +213,26 @@ func formatReportMessage(reportsBySeverityKind map[scanner.SeverityScoreKind][]s
209213
return
210214
}
211215

216+
func publishAsGeneralSlackMessageSingleChannel(channelName string, summary []goslack.MsgOption, threadMsgs []goslack.MsgOption, s slack.IService) (err error) {
217+
ts, err := s.PostMessage(channelName, summary...)
218+
if err != nil {
219+
return errors.Join(errors.New("failed to post slack summary"), err)
220+
}
221+
222+
for _, option := range threadMsgs {
223+
_, err = s.PostMessage(
224+
channelName,
225+
option,
226+
goslack.MsgOptionTS(ts), // Replies to the summary message in thread
227+
)
228+
if err != nil {
229+
return errors.Join(errors.New("failed to message in slack summary thread"), err)
230+
}
231+
}
232+
233+
return
234+
}
235+
212236
// splitMessage splits a string into chunks of at most maxLen characters.
213237
// Each chunk is determined by the closest newline character
214238
func splitMessage(s string, maxLen int) []string {

internal/publish/to_slack_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,28 @@ func TestPublishAsGeneralSlackMessage(t *testing.T) {
2525
},
2626
}
2727

28-
err := PublishAsGeneralSlackMessage("channel", report, []string{"path/to/group", "path/to/project"}, mockSlackService)
28+
err := PublishAsGeneralSlackMessage([]string{"channel"}, report, []string{"path/to/group", "path/to/project"}, mockSlackService)
29+
30+
assert.Nil(t, err)
31+
mockSlackService.AssertExpectations(t)
32+
}
33+
34+
func TestPublishAsGeneralSlackMessageToMultipleChannel(t *testing.T) {
35+
mockSlackService := &mockSlackService{}
36+
mockSlackService.On("PostMessage", "channel1", mock.Anything).Return("", nil)
37+
mockSlackService.On("PostMessage", "channel2", mock.Anything).Return("", nil)
38+
report := []scanner.Report{
39+
{
40+
IsVulnerable: true,
41+
Vulnerabilities: []scanner.Vulnerability{
42+
{
43+
Id: "CVE-2021-1234",
44+
},
45+
},
46+
},
47+
}
48+
49+
err := PublishAsGeneralSlackMessage([]string{"channel1", "channel2"}, report, []string{"path/to/group", "path/to/project"}, mockSlackService)
2950

3051
assert.Nil(t, err)
3152
mockSlackService.AssertExpectations(t)

0 commit comments

Comments
 (0)