package github import ( "context" "fmt" "net/http" "strings" "sync" "time" "github.com/google/go-github/github" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/selfstat" "golang.org/x/oauth2" ) // GitHub - plugin main structure type GitHub struct { Repositories []string `toml:"repositories"` AccessToken string `toml:"access_token"` HTTPTimeout internal.Duration `toml:"http_timeout"` githubClient *github.Client obfusticatedToken string RateLimit selfstat.Stat RateLimitErrors selfstat.Stat RateRemaining selfstat.Stat } const sampleConfig = ` ## List of repositories to monitor. repositories = ["influxdata/telegraf"] ## Github API access token. Unauthenticated requests are limited to 60 per hour. # access_token = "" ## Timeout for HTTP requests. # http_timeout = "5s" ` // SampleConfig returns sample configuration for this plugin. func (g *GitHub) SampleConfig() string { return sampleConfig } // Description returns the plugin description. func (g *GitHub) Description() string { return "Gather repository information from GitHub hosted repositories." } // Create GitHub Client func (g *GitHub) createGitHubClient(ctx context.Context) (*github.Client, error) { httpClient := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, }, Timeout: g.HTTPTimeout.Duration, } g.obfusticatedToken = "Unauthenticated" if g.AccessToken != "" { tokenSource := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: g.AccessToken}, ) oauthClient := oauth2.NewClient(ctx, tokenSource) ctx = context.WithValue(ctx, oauth2.HTTPClient, oauthClient) g.obfusticatedToken = g.AccessToken[0:4] + "..." + g.AccessToken[len(g.AccessToken)-3:] return github.NewClient(oauthClient), nil } return github.NewClient(httpClient), nil } // Gather GitHub Metrics func (g *GitHub) Gather(acc telegraf.Accumulator) error { ctx := context.Background() if g.githubClient == nil { githubClient, err := g.createGitHubClient(ctx) if err != nil { return err } g.githubClient = githubClient tokenTags := map[string]string{ "access_token": g.obfusticatedToken, } g.RateLimitErrors = selfstat.Register("github", "rate_limit_blocks", tokenTags) g.RateLimit = selfstat.Register("github", "rate_limit_limit", tokenTags) g.RateRemaining = selfstat.Register("github", "rate_limit_remaining", tokenTags) } var wg sync.WaitGroup wg.Add(len(g.Repositories)) for _, repository := range g.Repositories { go func(repositoryName string, acc telegraf.Accumulator) { defer wg.Done() owner, repository, err := splitRepositoryName(repositoryName) if err != nil { acc.AddError(err) return } repositoryInfo, response, err := g.githubClient.Repositories.Get(ctx, owner, repository) if _, ok := err.(*github.RateLimitError); ok { g.RateLimitErrors.Incr(1) } if err != nil { acc.AddError(err) return } g.RateLimit.Set(int64(response.Rate.Limit)) g.RateRemaining.Set(int64(response.Rate.Remaining)) now := time.Now() tags := getTags(repositoryInfo) fields := getFields(repositoryInfo) acc.AddFields("github_repository", fields, tags, now) }(repository, acc) } wg.Wait() return nil } func splitRepositoryName(repositoryName string) (string, string, error) { splits := strings.SplitN(repositoryName, "/", 2) if len(splits) != 2 { return "", "", fmt.Errorf("%v is not of format 'owner/repository'", repositoryName) } return splits[0], splits[1], nil } func getLicense(repositoryInfo *github.Repository) string { if repositoryInfo.GetLicense() != nil { return *repositoryInfo.License.Name } return "None" } func getTags(repositoryInfo *github.Repository) map[string]string { return map[string]string{ "owner": *repositoryInfo.Owner.Login, "name": *repositoryInfo.Name, "language": *repositoryInfo.Language, "license": getLicense(repositoryInfo), } } func getFields(repositoryInfo *github.Repository) map[string]interface{} { return map[string]interface{}{ "stars": *repositoryInfo.StargazersCount, "forks": *repositoryInfo.ForksCount, "open_issues": *repositoryInfo.OpenIssuesCount, "size": *repositoryInfo.Size, } } func init() { inputs.Add("github", func() telegraf.Input { return &GitHub{ HTTPTimeout: internal.Duration{Duration: time.Second * 5}, } }) }