github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/velodrome/fetcher/client.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 main 18 19 import ( 20 "context" 21 "fmt" 22 "io/ioutil" 23 "strings" 24 "time" 25 26 "golang.org/x/oauth2" 27 28 "github.com/golang/glog" 29 "github.com/google/go-github/github" 30 "github.com/spf13/cobra" 31 ) 32 33 // Client can be used to run commands again Github API 34 type Client struct { 35 Token string 36 TokenFile string 37 Org string 38 Project string 39 40 githubClient *github.Client 41 } 42 43 const ( 44 tokenLimit = 50 // We try to stop that far from the API limit 45 ) 46 47 // AddFlags parses options for github client 48 func (client *Client) AddFlags(cmd *cobra.Command) { 49 cmd.PersistentFlags().StringVar(&client.Token, "token", "", 50 "The OAuth Token to use for requests.") 51 cmd.PersistentFlags().StringVar(&client.TokenFile, "token-file", "", 52 "The file containing the OAuth Token to use for requests.") 53 cmd.PersistentFlags().StringVar(&client.Org, "organization", "", 54 "The github organization to scan") 55 cmd.PersistentFlags().StringVar(&client.Project, "project", "", 56 "The github project to scan") 57 } 58 59 // CheckFlags looks for organization and project flags to configure the client 60 func (client *Client) CheckFlags() error { 61 if client.Org == "" { 62 return fmt.Errorf("organization flag must be set") 63 } 64 client.Org = strings.ToLower(client.Org) 65 66 if client.Project == "" { 67 return fmt.Errorf("project flag must be set") 68 } 69 client.Project = strings.ToLower(client.Project) 70 71 return nil 72 } 73 74 // getGithubClient create the github client that we use to communicate with github 75 func (client *Client) getGithubClient() (*github.Client, error) { 76 if client.githubClient != nil { 77 return client.githubClient, nil 78 } 79 token := client.Token 80 if len(token) == 0 && len(client.TokenFile) != 0 { 81 data, err := ioutil.ReadFile(client.TokenFile) 82 if err != nil { 83 return nil, err 84 } 85 token = strings.TrimSpace(string(data)) 86 } 87 88 if len(token) > 0 { 89 ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) 90 tc := oauth2.NewClient(oauth2.NoContext, ts) 91 client.githubClient = github.NewClient(tc) 92 } else { 93 client.githubClient = github.NewClient(nil) 94 } 95 return client.githubClient, nil 96 } 97 98 // limitsCheckAndWait make sure we have not reached the limit or wait 99 func (client *Client) limitsCheckAndWait() { 100 var sleep time.Duration 101 githubClient, err := client.getGithubClient() 102 if err != nil { 103 glog.Error("Failed to get RateLimits: ", err) 104 sleep = time.Minute 105 } else { 106 limits, _, err := githubClient.RateLimits(context.Background()) 107 if err != nil { 108 glog.Error("Failed to get RateLimits:", err) 109 sleep = time.Minute 110 } 111 if limits != nil && limits.Core != nil && limits.Core.Remaining < tokenLimit { 112 sleep = limits.Core.Reset.Sub(time.Now()) 113 glog.Warning("RateLimits: reached. Sleeping for ", sleep) 114 } 115 } 116 117 time.Sleep(sleep) 118 } 119 120 // ClientInterface describes what a client should be able to do 121 type ClientInterface interface { 122 RepositoryName() string 123 FetchIssues(last time.Time, c chan *github.Issue) 124 FetchIssueEvents(issueID int, last *int, c chan *github.IssueEvent) 125 FetchIssueComments(issueID int, last time.Time, c chan *github.IssueComment) 126 FetchPullComments(issueID int, last time.Time, c chan *github.PullRequestComment) 127 } 128 129 // RepositoryName returns github's repository name in the form of org/project 130 func (client *Client) RepositoryName() string { 131 return fmt.Sprintf("%s/%s", client.Org, client.Project) 132 } 133 134 // FetchIssues from Github, until 'latest' time 135 func (client *Client) FetchIssues(latest time.Time, c chan *github.Issue) { 136 opt := &github.IssueListByRepoOptions{Since: latest, Sort: "updated", State: "all", Direction: "asc"} 137 138 githubClient, err := client.getGithubClient() 139 if err != nil { 140 close(c) 141 glog.Error(err) 142 return 143 } 144 145 count := 0 146 for { 147 client.limitsCheckAndWait() 148 149 issues, resp, err := githubClient.Issues.ListByRepo( 150 context.Background(), 151 client.Org, 152 client.Project, 153 opt, 154 ) 155 if err != nil { 156 close(c) 157 glog.Error(err) 158 return 159 } 160 161 for _, issue := range issues { 162 c <- issue 163 count++ 164 } 165 166 if resp.NextPage == 0 { 167 break 168 } 169 opt.ListOptions.Page = resp.NextPage 170 } 171 172 glog.Infof("Fetched %d issues updated issue since %v.", count, latest) 173 close(c) 174 } 175 176 // hasID look for a specific Id in a list of events 177 func hasID(events []*github.IssueEvent, ID int) bool { 178 for _, event := range events { 179 if *event.ID == ID { 180 return true 181 } 182 } 183 return false 184 } 185 186 // FetchIssueEvents from github and return the full list, until it matches 'latest' 187 // The entire last page will be included so you can have redundancy. 188 func (client *Client) FetchIssueEvents(issueID int, latest *int, c chan *github.IssueEvent) { 189 opt := &github.ListOptions{PerPage: 100} 190 191 githubClient, err := client.getGithubClient() 192 if err != nil { 193 close(c) 194 glog.Error(err) 195 return 196 } 197 198 count := 0 199 for { 200 client.limitsCheckAndWait() 201 202 events, resp, err := githubClient.Issues.ListIssueEvents( 203 context.Background(), 204 client.Org, 205 client.Project, 206 issueID, 207 opt, 208 ) 209 if err != nil { 210 glog.Errorf("ListIssueEvents failed: %s. Retrying...", err) 211 time.Sleep(time.Second) 212 continue 213 } 214 215 for _, event := range events { 216 c <- event 217 count++ 218 } 219 if resp.NextPage == 0 || (latest != nil && hasID(events, *latest)) { 220 break 221 } 222 opt.Page = resp.NextPage 223 } 224 225 glog.Infof("Fetched %d events.", count) 226 close(c) 227 } 228 229 // FetchIssueComments fetches comments associated to given Issue (since latest) 230 func (client *Client) FetchIssueComments(issueID int, latest time.Time, c chan *github.IssueComment) { 231 opt := &github.IssueListCommentsOptions{Since: latest, Sort: "updated", Direction: "asc"} 232 233 githubClient, err := client.getGithubClient() 234 if err != nil { 235 close(c) 236 glog.Error(err) 237 return 238 } 239 240 count := 0 241 for { 242 client.limitsCheckAndWait() 243 244 comments, resp, err := githubClient.Issues.ListComments( 245 context.Background(), 246 client.Org, 247 client.Project, 248 issueID, 249 opt, 250 ) 251 if err != nil { 252 close(c) 253 glog.Error(err) 254 return 255 } 256 257 for _, comment := range comments { 258 c <- comment 259 count++ 260 } 261 if resp.NextPage == 0 { 262 break 263 } 264 opt.ListOptions.Page = resp.NextPage 265 } 266 267 glog.Infof("Fetched %d issue comments updated since %v for issue #%d.", count, latest, issueID) 268 close(c) 269 } 270 271 // FetchPullComments fetches comments associated to given PullRequest (since latest) 272 func (client *Client) FetchPullComments(issueID int, latest time.Time, c chan *github.PullRequestComment) { 273 opt := &github.PullRequestListCommentsOptions{Since: latest, Sort: "updated", Direction: "asc"} 274 275 githubClient, err := client.getGithubClient() 276 if err != nil { 277 close(c) 278 glog.Error(err) 279 return 280 } 281 282 count := 0 283 for { 284 client.limitsCheckAndWait() 285 286 comments, resp, err := githubClient.PullRequests.ListComments( 287 context.Background(), 288 client.Org, 289 client.Project, 290 issueID, 291 opt, 292 ) 293 if err != nil { 294 close(c) 295 glog.Error(err) 296 return 297 } 298 299 for _, comment := range comments { 300 c <- comment 301 count++ 302 } 303 if resp.NextPage == 0 { 304 break 305 } 306 opt.ListOptions.Page = resp.NextPage 307 } 308 309 glog.Infof("Fetched %d review comments updated since %v for issue #%d.", count, latest, issueID) 310 close(c) 311 }