sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/heart/heart.go (about)

     1  /*
     2  Copyright 2017 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 heart
    18  
    19  import (
    20  	"fmt"
    21  	"math/rand"
    22  	"path/filepath"
    23  	"regexp"
    24  	"strings"
    25  
    26  	"github.com/sirupsen/logrus"
    27  
    28  	"sigs.k8s.io/prow/pkg/config"
    29  	"sigs.k8s.io/prow/pkg/github"
    30  	"sigs.k8s.io/prow/pkg/pluginhelp"
    31  	"sigs.k8s.io/prow/pkg/plugins"
    32  	"sigs.k8s.io/prow/pkg/plugins/ownersconfig"
    33  )
    34  
    35  const (
    36  	pluginName = "heart"
    37  )
    38  
    39  var reactions = []string{
    40  	github.ReactionThumbsUp,
    41  	github.ReactionHeart,
    42  	github.ReactionHooray,
    43  }
    44  
    45  func init() {
    46  	plugins.RegisterIssueCommentHandler(pluginName, handleIssueComment, helpProvider)
    47  	plugins.RegisterPullRequestHandler(pluginName, handlePullRequest, helpProvider)
    48  }
    49  
    50  func helpProvider(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
    51  	// The {WhoCanUse, Usage, Examples} fields are omitted because this plugin is not triggered with commands.
    52  	yamlSnippet, err := plugins.CommentMap.GenYaml(&plugins.Configuration{
    53  		Heart: plugins.Heart{
    54  			Adorees:       []string{"alice", "bob"},
    55  			CommentRegexp: ".*",
    56  		},
    57  	})
    58  	if err != nil {
    59  		logrus.WithError(err).Warnf("cannot generate comments for %s plugin", pluginName)
    60  	}
    61  	return &pluginhelp.PluginHelp{
    62  			Description: "The heart plugin celebrates certain GitHub actions with the reaction emojis. Emojis are added to pull requests that make additions to OWNERS or OWNERS_ALIASES files and to comments left by specified \"adorees\".",
    63  			Config: map[string]string{
    64  				"": fmt.Sprintf(
    65  					"The heart plugin is configured to react to comments,  satisfying the regular expression %s, left by the following GitHub users: %s.",
    66  					config.Heart.CommentRegexp,
    67  					strings.Join(config.Heart.Adorees, ", "),
    68  				),
    69  			},
    70  			Snippet: yamlSnippet,
    71  		},
    72  		nil
    73  }
    74  
    75  type githubClient interface {
    76  	CreateCommentReaction(org, repo string, ID int, reaction string) error
    77  	CreateIssueReaction(org, repo string, ID int, reaction string) error
    78  	GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error)
    79  }
    80  
    81  type client struct {
    82  	GitHubClient githubClient
    83  	Logger       *logrus.Entry
    84  }
    85  
    86  func getClient(pc plugins.Agent) client {
    87  	return client{
    88  		GitHubClient: pc.GitHubClient,
    89  		Logger:       pc.Logger,
    90  	}
    91  }
    92  
    93  func handleIssueComment(pc plugins.Agent, ic github.IssueCommentEvent) error {
    94  	if (pc.PluginConfig.Heart.Adorees == nil || len(pc.PluginConfig.Heart.Adorees) == 0) || len(pc.PluginConfig.Heart.CommentRegexp) == 0 {
    95  		return nil
    96  	}
    97  	return handleIC(getClient(pc), pc.PluginConfig.Heart.Adorees, pc.PluginConfig.Heart.CommentRe, ic)
    98  }
    99  
   100  func handlePullRequest(pc plugins.Agent, pre github.PullRequestEvent) error {
   101  	return handlePR(getClient(pc), pre, pc.PluginConfig.OwnersFilenames)
   102  }
   103  
   104  func handleIC(c client, adorees []string, commentRe *regexp.Regexp, ic github.IssueCommentEvent) error {
   105  	// Only consider new comments on PRs.
   106  	if !ic.Issue.IsPullRequest() || ic.Action != github.IssueCommentActionCreated {
   107  		return nil
   108  	}
   109  	adoredLogin := false
   110  	for _, login := range adorees {
   111  		if ic.Comment.User.Login == login {
   112  			adoredLogin = true
   113  			break
   114  		}
   115  	}
   116  	if !adoredLogin {
   117  		return nil
   118  	}
   119  
   120  	if !commentRe.MatchString(ic.Comment.Body) {
   121  		return nil
   122  	}
   123  
   124  	c.Logger.Info("This is a wonderful thing!")
   125  	return c.GitHubClient.CreateCommentReaction(
   126  		ic.Repo.Owner.Login,
   127  		ic.Repo.Name,
   128  		ic.Comment.ID,
   129  		reactions[rand.Intn(len(reactions))])
   130  }
   131  
   132  func handlePR(c client, pre github.PullRequestEvent, resolver ownersconfig.Resolver) error {
   133  	// Only consider newly opened PRs
   134  	if pre.Action != github.PullRequestActionOpened {
   135  		return nil
   136  	}
   137  
   138  	org := pre.PullRequest.Base.Repo.Owner.Login
   139  	repo := pre.PullRequest.Base.Repo.Name
   140  
   141  	changes, err := c.GitHubClient.GetPullRequestChanges(org, repo, pre.PullRequest.Number)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	// Smile at any change that adds to OWNERS files
   147  	for _, change := range changes {
   148  		_, filename := filepath.Split(change.Filename)
   149  		filenames := resolver(org, repo)
   150  		if (filename == filenames.Owners || filename == filenames.OwnersAliases) && change.Additions > 0 {
   151  			c.Logger.Info("Adding new OWNERS makes me happy!")
   152  			return c.GitHubClient.CreateIssueReaction(
   153  				pre.PullRequest.Base.Repo.Owner.Login,
   154  				pre.PullRequest.Base.Repo.Name,
   155  				pre.Number,
   156  				reactions[rand.Intn(len(reactions))])
   157  		}
   158  	}
   159  
   160  	return nil
   161  }