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 }