sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/reward-owners/reward-owners.go (about) 1 /* 2 Copyright 2021 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 rewardowners 18 19 import ( 20 "fmt" 21 "path/filepath" 22 "strings" 23 24 "github.com/sirupsen/logrus" 25 26 "k8s.io/apimachinery/pkg/util/sets" 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 "sigs.k8s.io/prow/pkg/repoowners" 34 ) 35 36 const ( 37 // PluginName defines this plugin's registered name. 38 PluginName = "reward-owners" 39 RewardMessage = ` 40 Thanks to %s for serving the community in this new capacity! 41 42 Next steps: 43 1- Reach out in [slack.k8s.io](http://slack.k8s.io/) -> #sig-contribex for your special badge swag! 44 45 2- Join [slack.k8s.io](http://slack.k8s.io/) -> #kubernetes-contributors in slack and [dev@kubernetes.io](https://groups.google.com/a/kubernetes.io/g/dev) for all upstream info 46 47 3- Review the [community-membership.md](https://github.com/kubernetes/community/blob/master/community-membership.md) doc for your role. If for some reason you can't perform the duties associated, Emeritus is a great way to take a break! [OWNERs](https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md) is another great resource for how this works. 48 49 4- Look over our [governance](https://github.com/kubernetes/community/blob/master/governance.md) docs now that you are actively involved in the maintenance of the project. 50 ` 51 ) 52 53 func init() { 54 plugins.RegisterPullRequestHandler(PluginName, handlePullRequest, helpProvider) 55 } 56 57 func helpProvider(_ *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) { 58 pluginHelp := &pluginhelp.PluginHelp{ 59 Description: fmt.Sprintf("The reward-owners plugin watches in %s and %s files for modifications and welcomes new approvers and reviewers.", ownersconfig.DefaultOwnersFile, ownersconfig.DefaultOwnersAliasesFile), 60 } 61 return pluginHelp, nil 62 } 63 64 type githubClient interface { 65 CreateComment(owner, repo string, number int, comment string) error 66 GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error) 67 } 68 69 type ownersClient interface { 70 LoadRepoOwnersSha(org, repo, base, sha string, updateCache bool) (repoowners.RepoOwner, error) 71 } 72 73 type info struct { 74 base github.PullRequestBranch 75 head github.PullRequestBranch 76 number int 77 org string 78 repo string 79 repoFullName string 80 } 81 82 func handlePullRequest(pc plugins.Agent, pre github.PullRequestEvent) error { 83 // Only consider closed PRs that got merged 84 if pre.Action != github.PullRequestActionClosed || !pre.PullRequest.Merged { 85 return nil 86 } 87 88 prInfo := info{ 89 base: pre.PullRequest.Base, 90 head: pre.PullRequest.Head, 91 number: pre.Number, 92 org: pre.Repo.Owner.Login, 93 repo: pre.Repo.Name, 94 repoFullName: pre.Repo.FullName, 95 } 96 return handle(pc.GitHubClient, pc.OwnersClient, pc.Logger, prInfo, pc.PluginConfig.OwnersFilenames) 97 } 98 99 func handle(ghc githubClient, oc ownersClient, log *logrus.Entry, info info, resolver ownersconfig.Resolver) error { 100 101 // Get changes. 102 changes, err := ghc.GetPullRequestChanges(info.org, info.repo, info.number) 103 if err != nil { 104 return fmt.Errorf("error getting PR changes: %w", err) 105 } 106 107 // Check if OWNERS or OWNERS_ALIASES have been modified. 108 var ownersModified bool 109 filenames := resolver(info.org, info.repo) 110 for _, change := range changes { 111 if (filepath.Base(change.Filename) == filenames.Owners || change.Filename == filenames.OwnersAliases) && 112 change.Status != github.PullRequestFileRemoved { 113 ownersModified = true 114 break 115 } 116 } 117 if !ownersModified { 118 return nil 119 } 120 121 log.Debug("Resolving repository owners for base branch...") 122 baseRepo, err := oc.LoadRepoOwnersSha(info.org, info.repo, info.base.Ref, info.base.SHA, false) 123 if err != nil { 124 return err 125 } 126 127 log.Debug("Resolving repository owners for head branch...") 128 headRepo, err := oc.LoadRepoOwnersSha(info.org, info.repo, info.head.Ref, info.head.SHA, false) 129 if err != nil { 130 return err 131 } 132 133 // Reward only new owners. 134 newOwners := headRepo.AllOwners().Difference(baseRepo.AllOwners()) 135 if newOwners.Len() == 0 { 136 log.Debug("No new owner to reward, exiting.") 137 return nil 138 } 139 140 // Tag users by prepending @ to their names. 141 taggedNewOwners := make([]string, newOwners.Len()) 142 for i, o := range sets.List(newOwners) { 143 taggedNewOwners[i] = fmt.Sprintf("@%s", o) 144 } 145 146 return ghc.CreateComment(info.org, info.repo, info.number, fmt.Sprintf(RewardMessage, strings.Join(taggedNewOwners, ", "))) 147 }