forked from cpanato/github_actions_exporter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
85d2f0a
commit 4474ee7
Showing
10 changed files
with
527 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package server | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"github.com/google/go-github/v47/github" | ||
"strconv" | ||
) | ||
|
||
const ( | ||
pageSize = 100 | ||
) | ||
|
||
type GitHubClient interface { | ||
GetOrganisationRunnerGroups(ctx context.Context, orgName string) ([]*github.RunnerGroup, error) | ||
GetEnterpriseRunners(ctx context.Context, enterpriseName string) ([]*github.Runner, error) | ||
GetGroupRunners(ctx context.Context, groupID int64, orgName string) ([]*github.Runner, error) | ||
GetActionsBillingOrg(ctx context.Context, org string) (*github.ActionBilling, error) | ||
GetActionsBillingUser(ctx context.Context, user string) (*github.ActionBilling, error) | ||
} | ||
|
||
type DefaultGitHubClient struct { | ||
Client *github.Client | ||
Opts *Opts | ||
} | ||
|
||
func NewGitHubClient(opts *Opts, client *github.Client) *DefaultGitHubClient { | ||
return &DefaultGitHubClient{Client: client, Opts: opts} | ||
} | ||
|
||
func (c *DefaultGitHubClient) GetOrganisationRunnerGroups(ctx context.Context, orgName string) ([]*github.RunnerGroup, error) { | ||
nextPage := 1 | ||
var allGroups []*github.RunnerGroup | ||
|
||
for nextPage > 0 { | ||
runnerGroups, response, err := c.Client.Actions.ListOrganizationRunnerGroups(ctx, orgName, &github.ListOrgRunnerGroupOptions{ | ||
ListOptions: github.ListOptions{ | ||
Page: nextPage, | ||
PerPage: pageSize, | ||
}, | ||
}) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if response.StatusCode != 200 { | ||
return nil, errors.New("unexpected response from GitHub API: " + strconv.Itoa(response.StatusCode)) | ||
} | ||
|
||
allGroups = append(allGroups, runnerGroups.RunnerGroups...) | ||
nextPage = response.NextPage | ||
} | ||
|
||
return allGroups, nil | ||
} | ||
|
||
func (c *DefaultGitHubClient) GetEnterpriseRunners(ctx context.Context, enterpriseName string) ([]*github.Runner, error) { | ||
var enterpriseRunners []*github.Runner | ||
var nextPage = 1 | ||
|
||
for nextPage > 0 { | ||
runners, response, err := c.Client.Enterprise.ListRunners(ctx, enterpriseName, &github.ListOptions{ | ||
Page: nextPage, | ||
PerPage: pageSize, | ||
}) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if response.StatusCode != 200 { | ||
return nil, errors.New("unexpected response from GitHub API: " + strconv.Itoa(response.StatusCode)) | ||
} | ||
|
||
enterpriseRunners = append(enterpriseRunners, runners.Runners...) | ||
nextPage = response.NextPage | ||
} | ||
|
||
return enterpriseRunners, nil | ||
} | ||
|
||
func (c *DefaultGitHubClient) GetGroupRunners(ctx context.Context, groupID int64, orgName string) ([]*github.Runner, error) { | ||
var groupRunners []*github.Runner | ||
var nextPage = 1 | ||
|
||
for nextPage > 0 { | ||
runners, response, err := c.Client.Actions.ListRunnerGroupRunners(ctx, orgName, groupID, &github.ListOptions{ | ||
Page: nextPage, | ||
PerPage: pageSize, | ||
}) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if response.StatusCode != 200 { | ||
return nil, errors.New("unexpected response from GitHub API: " + strconv.Itoa(response.StatusCode)) | ||
} | ||
|
||
groupRunners = append(groupRunners, runners.Runners...) | ||
nextPage = response.NextPage | ||
} | ||
|
||
return groupRunners, nil | ||
} | ||
|
||
func (c *DefaultGitHubClient) GetActionsBillingOrg(ctx context.Context, org string) (*github.ActionBilling, error) { | ||
billing, _, err := c.Client.Billing.GetActionsBillingOrg(ctx, org) | ||
return billing, err | ||
} | ||
|
||
func (c *DefaultGitHubClient) GetActionsBillingUser(ctx context.Context, user string) (*github.ActionBilling, error) { | ||
billing, _, err := c.Client.Billing.GetActionsBillingUser(ctx, user) | ||
return billing, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package server | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"time" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/go-kit/log/level" | ||
"github.com/google/go-github/v47/github" | ||
) | ||
|
||
type RunnersMetricsExporter struct { | ||
GHClient GitHubClient | ||
Logger log.Logger | ||
Opts Opts | ||
Observer RunnersObserver | ||
} | ||
|
||
func NewRunnersMetricsExporter(logger log.Logger, opts Opts, client GitHubClient, observer RunnersObserver) *RunnersMetricsExporter { | ||
return &RunnersMetricsExporter{ | ||
Logger: logger, | ||
Opts: opts, | ||
GHClient: client, | ||
Observer: observer, | ||
} | ||
} | ||
|
||
func (c *RunnersMetricsExporter) Start(ctx context.Context) error { | ||
if c.Opts.GitHubOrg == "" { | ||
return errors.New("github org not configured") | ||
} | ||
if c.Opts.GitHubAPIToken == "" { | ||
return errors.New("github token not configured") | ||
} | ||
|
||
ticker := time.NewTicker(time.Duration(c.Opts.RunnersAPIPollSeconds) * time.Second) | ||
go func() { | ||
for { | ||
select { | ||
case <-ticker.C: | ||
c.collectRunnersInformation(ctx) | ||
case <-ctx.Done(): | ||
_ = level.Info(c.Logger).Log("msg", "stopped polling for runner metrics") | ||
return | ||
} | ||
} | ||
}() | ||
|
||
return nil | ||
} | ||
|
||
func (c *RunnersMetricsExporter) collectRunnersInformation(ctx context.Context) { | ||
// Resetting, otherwise a certain label combination might retain its old value despite not being present in the pool | ||
// For example, if there are no busy runners then group[true] will be empty and the old value of group[true] will | ||
// continue to be reported rather than set to 0 as expected. Same would be true if API calls fail so we reset first. | ||
c.Observer.ResetRegisteredRunnersTotal() | ||
|
||
allRunners := make(map[string][]*github.Runner) | ||
runnerGroups, err := c.GHClient.GetOrganisationRunnerGroups(ctx, c.Opts.GitHubOrg) | ||
|
||
if err != nil { | ||
_ = level.Error(c.Logger).Log("msg", "unable to retrieve runner groups", "error", err.Error()) | ||
return | ||
} | ||
|
||
for _, runnerGroup := range runnerGroups { | ||
groupRunners, err := c.GHClient.GetGroupRunners(ctx, *runnerGroup.ID, c.Opts.GitHubOrg) | ||
|
||
if err != nil { | ||
_ = level.Error(c.Logger).Log("msg", "unable to retrieve organisation runners' info", "error", err.Error()) | ||
return | ||
} | ||
|
||
allRunners[*runnerGroup.Name] = groupRunners | ||
} | ||
|
||
// Collect information from the Enterprise runners, if an Enterprise name has been configured. | ||
// Requires the GitHub API Token to have manage_runners:enterprise scope. | ||
if c.Opts.GitHubEnterprise != "" { | ||
enterpriseRunners, err := c.GHClient.GetEnterpriseRunners(ctx, c.Opts.GitHubEnterprise) | ||
|
||
// We are putting the enterprise runners into a fake runner group named after the enterprise | ||
// This is because we already have that name in Grafana and also because there is no way in the API at the moment | ||
// to tie them to their real runner group | ||
allRunners[c.Opts.GitHubEnterprise] = enterpriseRunners | ||
|
||
if err != nil { | ||
_ = level.Error(c.Logger).Log("msg", "unable to retrieve enterprise runners' info", "error", err.Error()) | ||
return | ||
} | ||
} | ||
|
||
for group, runners := range allRunners { | ||
for _, runner := range runners { | ||
c.Observer.IncreaseRegisteredRunnersTotal(runner.GetBusy(), runner.GetStatus(), group) | ||
} | ||
} | ||
} |
Oops, something went wrong.