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  }