github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/plugins/trigger/pr.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 trigger 18 19 import ( 20 "fmt" 21 22 "k8s.io/test-infra/prow/github" 23 "k8s.io/test-infra/prow/kube" 24 "k8s.io/test-infra/prow/pjutil" 25 "k8s.io/test-infra/prow/plugins" 26 ) 27 28 const ( 29 needsOkToTest = "needs-ok-to-test" 30 ) 31 32 func handlePR(c client, trustedOrg string, pr github.PullRequestEvent) error { 33 author := pr.PullRequest.User.Login 34 switch pr.Action { 35 case github.PullRequestActionOpened: 36 // When a PR is opened, if the author is in the org then build it. 37 // Otherwise, ask for "/ok-to-test". There's no need to look for previous 38 // "/ok-to-test" comments since the PR was just opened! 39 member, err := c.GitHubClient.IsMember(trustedOrg, author) 40 if err != nil { 41 return fmt.Errorf("could not check membership: %s", err) 42 } else if member { 43 c.Logger.Info("Starting all jobs for new PR.") 44 return buildAll(c, pr.PullRequest) 45 } else { 46 c.Logger.Infof("Welcome message to PR author %q.", author) 47 if err := welcomeMsg(c.GitHubClient, pr.PullRequest, trustedOrg); err != nil { 48 return fmt.Errorf("could not welcome non-org member %q: %v", author, err) 49 } 50 } 51 case github.PullRequestActionReopened, github.PullRequestActionSynchronize: 52 // When a PR is updated, check that the user is in the org or that an org 53 // member has said "/ok-to-test" before building. There's no need to ask 54 // for "/ok-to-test" because we do that once when the PR is created. 55 trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg) 56 if err != nil { 57 return fmt.Errorf("could not validate PR: %s", err) 58 } else if trusted { 59 c.Logger.Info("Starting all jobs for updated PR.") 60 return buildAll(c, pr.PullRequest) 61 } 62 case github.PullRequestActionLabeled: 63 // When a PR is LGTMd, if it is untrusted then build it once. 64 if pr.Label.Name == lgtmLabel { 65 trusted, err := trustedPullRequest(c.GitHubClient, pr.PullRequest, trustedOrg) 66 if err != nil { 67 return fmt.Errorf("could not validate PR: %s", err) 68 } else if !trusted { 69 c.Logger.Info("Starting all jobs for untrusted PR with LGTM.") 70 return buildAll(c, pr.PullRequest) 71 } 72 } 73 } 74 return nil 75 } 76 77 func welcomeMsg(ghc githubClient, pr github.PullRequest, trustedOrg string) error { 78 commentTemplate := `Hi @%s. Thanks for your PR. 79 80 I'm waiting for a [%s](https://github.com/orgs/%s/people) member to verify that this patch is reasonable to test. If it is, they should reply with ` + "`/ok-to-test`" + ` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step. 81 82 I understand the commands that are listed [here](https://github.com/kubernetes/test-infra/blob/master/commands.md). 83 84 <details> 85 86 %s 87 </details> 88 ` 89 comment := fmt.Sprintf(commentTemplate, pr.User.Login, trustedOrg, trustedOrg, plugins.AboutThisBot) 90 91 owner := pr.Base.Repo.Owner.Login 92 name := pr.Base.Repo.Name 93 err1 := ghc.AddLabel(owner, name, pr.Number, needsOkToTest) 94 err2 := ghc.CreateComment(owner, name, pr.Number, comment) 95 if err1 != nil || err2 != nil { 96 return fmt.Errorf("welcomeMsg: error adding label: %v, error creating comment: %v", err1, err2) 97 } 98 return nil 99 } 100 101 // trustedPullRequest returns whether or not the given PR should be tested. 102 // It first checks if the author is in the org, then looks for "/ok-to-test" 103 // comments by org members. 104 func trustedPullRequest(ghc githubClient, pr github.PullRequest, trustedOrg string) (bool, error) { 105 author := pr.User.Login 106 // First check if the author is a member of the org. 107 orgMember, err := ghc.IsMember(trustedOrg, author) 108 if err != nil { 109 return false, err 110 } else if orgMember { 111 return true, nil 112 } 113 // Next look for "/ok-to-test" comments on the PR. 114 comments, err := ghc.ListIssueComments(pr.Base.Repo.Owner.Login, pr.Base.Repo.Name, pr.Number) 115 if err != nil { 116 return false, err 117 } 118 for _, comment := range comments { 119 commentAuthor := comment.User.Login 120 // Skip comments by the PR author. 121 if commentAuthor == author { 122 continue 123 } 124 // Skip bot comments. 125 botName, err := ghc.BotName() 126 if err != nil { 127 return false, err 128 } 129 if commentAuthor == botName { 130 continue 131 } 132 // Look for "/ok-to-test" 133 if !okToTest.MatchString(comment.Body) { 134 continue 135 } 136 // Ensure that the commenter is in the org. 137 commentAuthorMember, err := ghc.IsMember(trustedOrg, commentAuthor) 138 if err != nil { 139 return false, err 140 } else if commentAuthorMember { 141 return true, nil 142 } 143 } 144 return false, nil 145 } 146 147 func buildAll(c client, pr github.PullRequest) error { 148 org := pr.Base.Repo.Owner.Login 149 repo := pr.Base.Repo.Name 150 var ref string 151 var changes []string // lazily initialized 152 153 for _, job := range c.Config.Presubmits[pr.Base.Repo.FullName] { 154 skip := false 155 if job.RunIfChanged != "" { 156 if changes == nil { 157 changesFull, err := c.GitHubClient.GetPullRequestChanges(org, repo, pr.Number) 158 if err != nil { 159 return err 160 } 161 // We only care about the filenames here 162 for _, change := range changesFull { 163 changes = append(changes, change.Filename) 164 } 165 } 166 skip = !job.RunsAgainstChanges(changes) 167 } else if !job.AlwaysRun { 168 continue 169 } 170 171 if (skip || !job.RunsAgainstBranch(pr.Base.Ref)) && !job.SkipReport { 172 if err := c.GitHubClient.CreateStatus(org, repo, pr.Head.SHA, github.Status{ 173 State: github.StatusSuccess, 174 Context: job.Context, 175 Description: "Skipped", 176 }); err != nil { 177 return err 178 } 179 continue 180 } 181 182 // Only get master ref once. 183 if ref == "" { 184 r, err := c.GitHubClient.GetRef(org, repo, "heads/"+pr.Base.Ref) 185 if err != nil { 186 return err 187 } 188 ref = r 189 } 190 kr := kube.Refs{ 191 Org: org, 192 Repo: repo, 193 BaseRef: pr.Base.Ref, 194 BaseSHA: ref, 195 Pulls: []kube.Pull{ 196 { 197 Number: pr.Number, 198 Author: pr.User.Login, 199 SHA: pr.Head.SHA, 200 }, 201 }, 202 } 203 if _, err := c.KubeClient.CreateProwJob(pjutil.NewProwJob(pjutil.PresubmitSpec(job, kr))); err != nil { 204 return err 205 } 206 } 207 return nil 208 }