github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/skip/skip.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 skip implements the `/skip` command which allows users 18 // to clean up commit statuses of non-blocking presubmits on PRs. 19 package skip 20 21 import ( 22 "fmt" 23 "regexp" 24 25 "github.com/sirupsen/logrus" 26 27 "k8s.io/test-infra/prow/config" 28 "k8s.io/test-infra/prow/github" 29 "k8s.io/test-infra/prow/pluginhelp" 30 "k8s.io/test-infra/prow/plugins" 31 ) 32 33 const pluginName = "skip" 34 35 var ( 36 skipRe = regexp.MustCompile(`(?mi)^/skip\s*$`) 37 ) 38 39 type githubClient interface { 40 CreateComment(owner, repo string, number int, comment string) error 41 CreateStatus(org, repo, ref string, s github.Status) error 42 GetPullRequest(org, repo string, number int) (*github.PullRequest, error) 43 GetPullRequestChanges(org, repo string, number int) ([]github.PullRequestChange, error) 44 ListStatuses(org, repo, ref string) ([]github.Status, error) 45 } 46 47 func init() { 48 plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider) 49 } 50 51 func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) { 52 pluginHelp := &pluginhelp.PluginHelp{ 53 Description: "The skip plugin allows users to clean up Github stale commit statuses for non-blocking jobs on a PR.", 54 } 55 pluginHelp.AddCommand(pluginhelp.Command{ 56 Usage: "/skip", 57 Description: "Cleans up Github stale commit statuses for non-blocking jobs on a PR.", 58 Featured: false, 59 WhoCanUse: "Anyone can trigger this command on a PR.", 60 Examples: []string{"/skip"}, 61 }) 62 return pluginHelp, nil 63 } 64 65 func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error { 66 return handle(pc.GitHubClient, pc.Logger, &e, pc.Config.Presubmits[e.Repo.FullName]) 67 } 68 69 func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, presubmits []config.Presubmit) error { 70 if !e.IsPR || e.IssueState != "open" || e.Action != github.GenericCommentActionCreated { 71 return nil 72 } 73 74 if !skipRe.MatchString(e.Body) { 75 return nil 76 } 77 78 org := e.Repo.Owner.Login 79 repo := e.Repo.Name 80 number := e.Number 81 82 pr, err := gc.GetPullRequest(org, repo, number) 83 if err != nil { 84 resp := fmt.Sprintf("Cannot get PR #%d in %s/%s: %v", number, org, repo, err) 85 log.Warn(resp) 86 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp)) 87 } 88 89 changesFull, err := gc.GetPullRequestChanges(org, repo, number) 90 if err != nil { 91 resp := fmt.Sprintf("Cannot get changes for PR #%d in %s/%s: %v", number, org, repo, err) 92 log.Warn(resp) 93 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp)) 94 } 95 var changes []string 96 for _, change := range changesFull { 97 changes = append(changes, change.Filename) 98 } 99 100 statuses, err := gc.ListStatuses(org, repo, pr.Head.SHA) 101 if err != nil { 102 resp := fmt.Sprintf("Cannot get commit statuses for PR #%d in %s/%s: %v", number, org, repo, err) 103 log.Warn(resp) 104 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp)) 105 } 106 107 for _, job := range presubmits { 108 // Ignore blocking jobs. 109 if !job.SkipReport && job.AlwaysRun { 110 continue 111 } 112 // Ignore jobs that need to run against the PR changes. 113 if !job.SkipReport && job.RunIfChanged != "" && job.RunsAgainstChanges(changes) { 114 continue 115 } 116 // Ignore jobs that don't have a status yet. 117 if !statusExists(job, statuses) { 118 continue 119 } 120 // Ignore jobs that have a green status. 121 if !isNotSuccess(job, statuses) { 122 continue 123 } 124 context := job.Context 125 status := github.Status{ 126 State: github.StatusSuccess, 127 Description: "Skipped", 128 Context: context, 129 } 130 if err := gc.CreateStatus(org, repo, pr.Head.SHA, status); err != nil { 131 resp := fmt.Sprintf("Cannot update PR status for context %s: %v", context, err) 132 log.Warn(resp) 133 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp)) 134 } 135 } 136 return nil 137 } 138 139 func statusExists(job config.Presubmit, statuses []github.Status) bool { 140 for _, status := range statuses { 141 if status.Context == job.Context { 142 return true 143 } 144 } 145 return false 146 } 147 148 func isNotSuccess(job config.Presubmit, statuses []github.Status) bool { 149 for _, status := range statuses { 150 if status.Context == job.Context && status.State != github.StatusSuccess { 151 return true 152 } 153 } 154 return false 155 }