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