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  }