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  }