github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/velodrome/token-counter/token-counter.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 "errors" 22 "flag" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 "time" 30 31 "github.com/golang/glog" 32 "github.com/google/go-github/github" 33 "github.com/spf13/cobra" 34 "golang.org/x/oauth2" 35 ) 36 37 type tokenCounterFlags struct { 38 influx InfluxConfig 39 tokens []string 40 } 41 42 func (flags *tokenCounterFlags) AddFlags(cmd *cobra.Command) { 43 cmd.Flags().StringSliceVar(&flags.tokens, "token", []string{}, "List of tokens") 44 cmd.Flags().AddGoFlagSet(flag.CommandLine) 45 } 46 47 // TokenHandler is refreshing token usage 48 type TokenHandler struct { 49 gClient *github.Client 50 influxdb *InfluxDB 51 login string 52 } 53 54 // GetGithubClient creates a client for each token 55 func GetGithubClient(token string) *github.Client { 56 return github.NewClient( 57 oauth2.NewClient( 58 oauth2.NoContext, 59 oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}), 60 ), 61 ) 62 } 63 64 // GetUsername finds the login for each token 65 func GetUsername(client *github.Client) (string, error) { 66 user, _, err := client.Users.Get(context.Background(), "") 67 if err != nil { 68 return "", err 69 } 70 if user.Login == nil { 71 return "", errors.New("Users.Get(\"\") returned empty login.") 72 } 73 74 return *user.Login, nil 75 } 76 77 // CreateTokenHandler parses the token and create a handler 78 func CreateTokenHandler(tokenStream io.Reader, influxdb *InfluxDB) (*TokenHandler, error) { 79 token, err := ioutil.ReadAll(tokenStream) 80 if err != nil { 81 return nil, err 82 } 83 client := GetGithubClient(strings.TrimSpace(string(token))) 84 login, err := GetUsername(client) // Get user name for token 85 if err != nil { 86 return nil, err 87 } 88 89 return &TokenHandler{ 90 gClient: client, 91 login: login, 92 influxdb: influxdb, 93 }, nil 94 } 95 96 // CreateTokenHandlers goes through the list of token files, and create handlers 97 func CreateTokenHandlers(tokenFiles []string, influxdb *InfluxDB) ([]TokenHandler, error) { 98 tokens := []TokenHandler{} 99 for _, tokenFile := range tokenFiles { 100 f, err := os.Open(tokenFile) 101 if err != nil { 102 return nil, fmt.Errorf("Can't open token-file (%s): %s", tokenFile, err) 103 } 104 token, err := CreateTokenHandler(f, influxdb) 105 if err != nil { 106 return nil, fmt.Errorf("Failed to create token (%s): %s", tokenFile, err) 107 } 108 tokens = append(tokens, *token) 109 } 110 return tokens, nil 111 } 112 113 func (t TokenHandler) getCoreRate() (*github.Rate, error) { 114 limits, _, err := t.gClient.RateLimits(context.Background()) 115 if err != nil { 116 return nil, err 117 } 118 return limits.Core, nil 119 } 120 121 // Process does the main job: 122 // It tries to get the value of "Remaining" rate just before the token 123 // gets reset. It does that more and more often (as the reset date gets 124 // closer) to get the most accurate value. 125 func (t TokenHandler) Process() { 126 lastRate, err := t.getCoreRate() 127 if err != nil { 128 glog.Fatalf("%s: Couldn't get rate limits: %v", t.login, err) 129 } 130 131 for { 132 halfPeriod := lastRate.Reset.Time.Sub(time.Now()) / 2 133 time.Sleep(halfPeriod) 134 newRate, err := t.getCoreRate() 135 if err != nil { 136 glog.Error("Failed to get CoreRate: ", err) 137 continue 138 } 139 // There is a bug in Github. They seem to reset the Remaining value before reseting the Reset value. 140 if !newRate.Reset.Time.Equal(lastRate.Reset.Time) || newRate.Remaining > lastRate.Remaining { 141 if err := t.influxdb.Push( 142 "github_token_count", 143 map[string]string{"login": t.login}, 144 map[string]interface{}{"value": lastRate.Limit - lastRate.Remaining}, 145 lastRate.Reset.Time, 146 ); err != nil { 147 glog.Error("Failed to push count:", err) 148 } 149 // Make sure the timer is properly reset, and we have time anyway 150 time.Sleep(30 * time.Minute) 151 for { 152 newRate, err = t.getCoreRate() 153 if err == nil { 154 break 155 } 156 glog.Error("Failed to get CoreRate: ", err) 157 time.Sleep(time.Minute) 158 } 159 160 } 161 lastRate = newRate 162 } 163 } 164 165 func runProgram(flags *tokenCounterFlags) error { 166 influxdb, err := flags.influx.CreateDatabaseClient() 167 if err != nil { 168 return err 169 } 170 171 tokens, err := CreateTokenHandlers(flags.tokens, influxdb) 172 if err != nil { 173 return err 174 } 175 176 if len(tokens) == 0 { 177 glog.Warning("No token given, nothing to do. Leaving...") 178 return nil 179 } 180 181 for _, token := range tokens { 182 go token.Process() 183 } 184 185 select {} 186 } 187 188 func main() { 189 flags := &tokenCounterFlags{} 190 cmd := &cobra.Command{ 191 Use: filepath.Base(os.Args[0]), 192 Short: "Count usage of github token", 193 RunE: func(_ *cobra.Command, _ []string) error { 194 return runProgram(flags) 195 }, 196 } 197 flags.AddFlags(cmd) 198 flags.influx.AddFlags(cmd) 199 200 if err := cmd.Execute(); err != nil { 201 glog.Error(err) 202 } 203 }