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  }