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 }