github.com/abayer/test-infra@v0.0.5/prow/plugins/slackevents/slackevents.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package slackevents 18 19 import ( 20 "fmt" 21 "regexp" 22 "strings" 23 24 "k8s.io/test-infra/prow/github" 25 "k8s.io/test-infra/prow/pluginhelp" 26 "k8s.io/test-infra/prow/plugins" 27 ) 28 29 const ( 30 pluginName = "slackevents" 31 ) 32 33 var sigMatcher = regexp.MustCompile(`(?m)@kubernetes/sig-([\w-]*)-(misc|test-failures|bugs|feature-requests|proposals|pr-reviews|api-reviews)`) 34 35 type slackClient interface { 36 WriteMessage(text string, channel string) error 37 } 38 39 type githubClient interface { 40 BotName() (string, error) 41 } 42 43 type client struct { 44 GithubClient githubClient 45 SlackClient slackClient 46 SlackConfig plugins.Slack 47 } 48 49 func init() { 50 plugins.RegisterPushEventHandler(pluginName, handlePush, helpProvider) 51 plugins.RegisterGenericCommentHandler(pluginName, handleComment, helpProvider) 52 } 53 54 func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) { 55 configInfo := map[string]string{ 56 "": fmt.Sprintf("SIG mentions on Github are reiterated for the following SIG Slack channels: %s.", strings.Join(config.Slack.MentionChannels, ", ")), 57 } 58 for _, repo := range enabledRepos { 59 parts := strings.Split(repo, "/") 60 if len(parts) != 2 { 61 return nil, fmt.Errorf("invalid repo in enabledRepos: %q", repo) 62 } 63 if mw := getMergeWarning(config.Slack.MergeWarnings, parts[0], parts[1]); mw != nil { 64 configInfo[repo] = fmt.Sprintf("In this repo merges are considered "+ 65 "manual and trigger manual merge warnings if the user who merged is not "+ 66 "a member of this universal whitelist: %s or merged to a branch they "+ 67 "are not specifically whitelisted for: %#v.<br>Warnings are sent to the "+ 68 "following Slack channels: %s.", strings.Join(mw.WhiteList, ", "), 69 mw.BranchWhiteList, strings.Join(mw.Channels, ", ")) 70 } else { 71 configInfo[repo] = "There are no manual merge warnings configured for this repo." 72 } 73 } 74 return &pluginhelp.PluginHelp{ 75 Description: `The slackevents plugin reacts to various Github events by commenting in Slack channels. 76 <ol><li>The plugin can create comments to alert on manual merges. Manual merges are merges made by a normal user instead of a bot or trusted user.</li> 77 <li>The plugin can create comments to reiterate SIG mentions like '@kubernetes/sig-testing-bugs' from Github.</li></ol>`, 78 Config: configInfo, 79 }, 80 nil 81 } 82 83 func handleComment(pc plugins.PluginClient, e github.GenericCommentEvent) error { 84 c := client{ 85 GithubClient: pc.GitHubClient, 86 SlackConfig: pc.PluginConfig.Slack, 87 SlackClient: pc.SlackClient, 88 } 89 return echoToSlack(c, e) 90 } 91 92 func handlePush(pc plugins.PluginClient, pe github.PushEvent) error { 93 c := client{ 94 GithubClient: pc.GitHubClient, 95 SlackConfig: pc.PluginConfig.Slack, 96 SlackClient: pc.SlackClient, 97 } 98 return notifyOnSlackIfManualMerge(c, pe) 99 } 100 101 func notifyOnSlackIfManualMerge(pc client, pe github.PushEvent) error { 102 //Fetch MergeWarning for the repo we received the merge event. 103 if mw := getMergeWarning(pc.SlackConfig.MergeWarnings, pe.Repo.Owner.Login, pe.Repo.Name); mw != nil { 104 //If the MergeWarning whitelist has the merge user then no need to send a message. 105 if wl := !isWhiteListed(mw, pe); wl { 106 message := fmt.Sprintf("*Warning:* %s (<@%s>) manually merged %s", pe.Sender.Login, pe.Sender.Login, pe.Compare) 107 for _, channel := range mw.Channels { 108 if err := pc.SlackClient.WriteMessage(message, channel); err != nil { 109 return err 110 } 111 } 112 } 113 } 114 return nil 115 } 116 117 func isWhiteListed(mw *plugins.MergeWarning, pe github.PushEvent) bool { 118 bwl := mw.BranchWhiteList[pe.Branch()] 119 inWhiteList := stringInArray(pe.Pusher.Name, mw.WhiteList) || stringInArray(pe.Sender.Login, mw.WhiteList) 120 inBranchWhiteList := stringInArray(pe.Pusher.Name, bwl) || stringInArray(pe.Sender.Login, bwl) 121 return inWhiteList || inBranchWhiteList 122 } 123 124 func getMergeWarning(mergeWarnings []plugins.MergeWarning, org, repo string) *plugins.MergeWarning { 125 for _, mw := range mergeWarnings { 126 if stringInArray(org, mw.Repos) || stringInArray(fmt.Sprintf("%s/%s", org, repo), mw.Repos) { 127 return &mw 128 } 129 } 130 return nil 131 } 132 133 func stringInArray(str string, list []string) bool { 134 for _, v := range list { 135 if v == str { 136 return true 137 } 138 } 139 return false 140 } 141 142 func echoToSlack(pc client, e github.GenericCommentEvent) error { 143 // Ignore bot comments and comments that aren't new. 144 botName, err := pc.GithubClient.BotName() 145 if err != nil { 146 return err 147 } 148 if e.User.Login == botName { 149 return nil 150 } 151 if e.Action != github.GenericCommentActionCreated { 152 return nil 153 } 154 155 sigMatches := sigMatcher.FindAllStringSubmatch(e.Body, -1) 156 157 for _, match := range sigMatches { 158 sig := "sig-" + match[1] 159 // Check if this sig is a slack channel that should be messaged. 160 found := false 161 for _, channel := range pc.SlackConfig.MentionChannels { 162 if channel == sig { 163 found = true 164 break 165 } 166 } 167 if !found { 168 continue 169 } 170 171 msg := fmt.Sprintf("%s was mentioned by %s (<@%s>) on Github. (%s)\n>>>%s", sig, e.User.Login, e.User.Login, e.HTMLURL, e.Body) 172 if err := pc.SlackClient.WriteMessage(msg, sig); err != nil { 173 return fmt.Errorf("Failed to send message on slack channel: %q with message %q. Err: %v", sig, msg, err) 174 } 175 } 176 return nil 177 }