github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/pkg/ghclient/wrappers.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  // This file contains the functions that mostly correspond with go-github functions, but add retry
    18  // logic, rate limiting, and pagination handling.
    19  
    20  package ghclient
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  
    26  	"github.com/golang/glog"
    27  
    28  	"github.com/google/go-github/github"
    29  )
    30  
    31  // The following interfaces are used for dependency injection in testing. They match go-github.
    32  
    33  type issueService interface {
    34  	Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error)
    35  	ListByRepo(ctx context.Context, org, repo string, opt *github.IssueListByRepoOptions) ([]*github.Issue, *github.Response, error)
    36  	ListLabels(ctx context.Context, owner, repo string, opt *github.ListOptions) ([]*github.Label, *github.Response, error)
    37  }
    38  
    39  type pullRequestService interface {
    40  	List(ctx context.Context, org, repo string, opt *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error)
    41  }
    42  
    43  type repositoryService interface {
    44  	CreateStatus(ctx context.Context, org, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error)
    45  	GetCombinedStatus(ctx context.Context, org, repo, ref string, opt *github.ListOptions) (*github.CombinedStatus, *github.Response, error)
    46  	ListCollaborators(ctx context.Context, owner, repo string, opt *github.ListOptions) ([]*github.User, *github.Response, error)
    47  }
    48  
    49  type usersService interface {
    50  	Get(ctx context.Context, login string) (*github.User, *github.Response, error)
    51  }
    52  
    53  // CreateIssue tries to create and return a new github issue.
    54  func (c *Client) CreateIssue(org, repo, title, body string, labels, assignees []string) (*github.Issue, error) {
    55  	glog.Infof("CreateIssue(dry=%t) Title:%q, Labels:%q, Assignees:%q\n", c.dryRun, title, labels, assignees)
    56  	if c.dryRun {
    57  		return nil, nil
    58  	}
    59  
    60  	issue := &github.IssueRequest{
    61  		Title: &title,
    62  		Body:  &body,
    63  	}
    64  	if len(labels) > 0 {
    65  		issue.Labels = &labels
    66  	}
    67  	if len(assignees) > 0 {
    68  		issue.Assignees = &assignees
    69  	}
    70  
    71  	var result *github.Issue
    72  	_, err := c.retry(
    73  		fmt.Sprintf("creating issue '%s'", title),
    74  		func() (*github.Response, error) {
    75  			var resp *github.Response
    76  			var err error
    77  			result, resp, err = c.issueService.Create(context.Background(), org, repo, issue)
    78  			return resp, err
    79  		},
    80  	)
    81  	return result, err
    82  }
    83  
    84  // CreateStatus creates or updates a status context on the indicated reference.
    85  func (c *Client) CreateStatus(owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, error) {
    86  	glog.Infof("CreateStatus(dry=%t) ref:%s: %s:%s", c.dryRun, ref, *status.Context, *status.State)
    87  	if c.dryRun {
    88  		return nil, nil
    89  	}
    90  	var result *github.RepoStatus
    91  	msg := fmt.Sprintf("creating status for ref '%s'", ref)
    92  	_, err := c.retry(msg, func() (*github.Response, error) {
    93  		var resp *github.Response
    94  		var err error
    95  		result, resp, err = c.repoService.CreateStatus(context.Background(), owner, repo, ref, status)
    96  		return resp, err
    97  	})
    98  	return result, err
    99  }
   100  
   101  type PRMungeFunc func(*github.PullRequest) error
   102  
   103  // ForEachPR iterates over all PRs that fit the specified criteria, calling the munge function on every PR.
   104  // If the munge function returns a non-nil error, ForEachPR will return immediately with a non-nil
   105  // error unless continueOnError is true in which case an error will be logged and the remaining PRs will be munged.
   106  func (c *Client) ForEachPR(owner, repo string, opts *github.PullRequestListOptions, continueOnError bool, mungePR PRMungeFunc) error {
   107  	var lastPage int
   108  	// Munge each page as we get it (or in other words, wait until we are ready to munge the next
   109  	// page of issues before getting it). We use depaginate to make the calls, but don't care about
   110  	// the slice it returns since we consume the pages as we go.
   111  	_, err := c.depaginate(
   112  		"processing PRs",
   113  		&opts.ListOptions,
   114  		func() ([]interface{}, *github.Response, error) {
   115  			list, resp, err := c.prService.List(context.Background(), owner, repo, opts)
   116  			if err == nil {
   117  				for _, pr := range list {
   118  					if pr == nil {
   119  						glog.Errorln("Received a nil PR from go-github while listing PRs. Skipping...")
   120  					}
   121  					if mungeErr := mungePR(pr); mungeErr != nil {
   122  						if pr.Number == nil {
   123  							mungeErr = fmt.Errorf("error munging pull request with nil Number field: %v", mungeErr)
   124  						} else {
   125  							mungeErr = fmt.Errorf("error munging pull request #%d: %v", *pr.Number, mungeErr)
   126  						}
   127  						if !continueOnError {
   128  							return nil, resp, &retryAbort{mungeErr}
   129  						}
   130  						glog.Errorf("%v\n", mungeErr)
   131  					}
   132  				}
   133  				if resp.LastPage > 0 {
   134  					lastPage = resp.LastPage
   135  				}
   136  				glog.Infof("ForEachPR processed page %d/%d\n", opts.ListOptions.Page, lastPage)
   137  			}
   138  			return nil, resp, err
   139  		},
   140  	)
   141  	return err
   142  }
   143  
   144  // GetCollaborators returns all github users who are members or outside collaborators of the repo.
   145  func (c *Client) GetCollaborators(org, repo string) ([]*github.User, error) {
   146  	opts := &github.ListOptions{}
   147  	collaborators, err := c.depaginate(
   148  		fmt.Sprintf("getting collaborators for '%s/%s'", org, repo),
   149  		opts,
   150  		func() ([]interface{}, *github.Response, error) {
   151  			page, resp, err := c.repoService.ListCollaborators(context.Background(), org, repo, opts)
   152  
   153  			var interfaceList []interface{}
   154  			if err == nil {
   155  				interfaceList = make([]interface{}, 0, len(page))
   156  				for _, user := range page {
   157  					interfaceList = append(interfaceList, user)
   158  				}
   159  			}
   160  			return interfaceList, resp, err
   161  		},
   162  	)
   163  
   164  	result := make([]*github.User, 0, len(collaborators))
   165  	for _, user := range collaborators {
   166  		result = append(result, user.(*github.User))
   167  	}
   168  	return result, err
   169  }
   170  
   171  // GetCombinedStatus retrieves the CombinedStatus for the specified reference.
   172  func (c *Client) GetCombinedStatus(owner, repo, ref string) (*github.CombinedStatus, error) {
   173  	var result *github.CombinedStatus
   174  	listOpts := &github.ListOptions{}
   175  
   176  	statuses, err := c.depaginate(
   177  		fmt.Sprintf("getting combined status for ref '%s'", ref),
   178  		listOpts,
   179  		func() ([]interface{}, *github.Response, error) {
   180  			combined, resp, err := c.repoService.GetCombinedStatus(
   181  				context.Background(),
   182  				owner,
   183  				repo,
   184  				ref,
   185  				listOpts,
   186  			)
   187  			if result == nil {
   188  				result = combined
   189  			}
   190  
   191  			var interfaceList []interface{}
   192  			if err == nil {
   193  				interfaceList = make([]interface{}, 0, len(combined.Statuses))
   194  				for _, status := range combined.Statuses {
   195  					interfaceList = append(interfaceList, status)
   196  				}
   197  			}
   198  			return interfaceList, resp, err
   199  		},
   200  	)
   201  
   202  	if result != nil {
   203  		result.Statuses = make([]github.RepoStatus, 0, len(statuses))
   204  		for _, status := range statuses {
   205  			result.Statuses = append(result.Statuses, status.(github.RepoStatus))
   206  		}
   207  	}
   208  
   209  	return result, err
   210  }
   211  
   212  // GetIssues gets all the issues in a repo that meet the list options.
   213  func (c *Client) GetIssues(org, repo string, opts *github.IssueListByRepoOptions) ([]*github.Issue, error) {
   214  	issues, err := c.depaginate(
   215  		fmt.Sprintf("getting issues from '%s/%s'", org, repo),
   216  		&opts.ListOptions,
   217  		func() ([]interface{}, *github.Response, error) {
   218  			page, resp, err := c.issueService.ListByRepo(context.Background(), org, repo, opts)
   219  
   220  			var interfaceList []interface{}
   221  			if err == nil {
   222  				interfaceList = make([]interface{}, 0, len(page))
   223  				for _, issue := range page {
   224  					interfaceList = append(interfaceList, issue)
   225  				}
   226  			}
   227  			return interfaceList, resp, err
   228  		},
   229  	)
   230  
   231  	result := make([]*github.Issue, 0, len(issues))
   232  	for _, issue := range issues {
   233  		result = append(result, issue.(*github.Issue))
   234  	}
   235  	return result, err
   236  }
   237  
   238  // GetRepoLabels gets all the labels that valid in the specified repo.
   239  func (c *Client) GetRepoLabels(org, repo string) ([]*github.Label, error) {
   240  	opts := &github.ListOptions{}
   241  	labels, err := c.depaginate(
   242  		fmt.Sprintf("getting valid labels for '%s/%s'", org, repo),
   243  		opts,
   244  		func() ([]interface{}, *github.Response, error) {
   245  			page, resp, err := c.issueService.ListLabels(context.Background(), org, repo, opts)
   246  
   247  			var interfaceList []interface{}
   248  			if err == nil {
   249  				interfaceList = make([]interface{}, 0, len(page))
   250  				for _, label := range page {
   251  					interfaceList = append(interfaceList, label)
   252  				}
   253  			}
   254  			return interfaceList, resp, err
   255  		},
   256  	)
   257  
   258  	result := make([]*github.Label, 0, len(labels))
   259  	for _, label := range labels {
   260  		result = append(result, label.(*github.Label))
   261  	}
   262  	return result, err
   263  }
   264  
   265  // GetUser gets the github user with the specified login or the currently authenticated user.
   266  // To get the currently authenticated user specify a login of "".
   267  func (c *Client) GetUser(login string) (*github.User, error) {
   268  	var result *github.User
   269  	_, err := c.retry(
   270  		fmt.Sprintf("getting user '%s'", login),
   271  		func() (*github.Response, error) {
   272  			var resp *github.Response
   273  			var err error
   274  			result, resp, err = c.userService.Get(context.Background(), login)
   275  			return resp, err
   276  		},
   277  	)
   278  	return result, err
   279  }