Support runner groups with selected visibility in webhooks autoscaler (#1012)

The current implementation doesn't support yet runner groups with custom visibility (e.g selected repositories only). If there are multiple runner groups with selected visibility - not all runner groups may be a potential target to be scaled up. Thus this PR introduces support to allow having runner groups with selected visibility. This requires to query GitHub API to find what are the potential runner groups that are linked to a specific repository (whether using visibility all or selected).

This also improves resolving the `scaleTargetKey` that are used to match an HRA based on the inputs of the `RunnerSet`/`RunnerDeployment` spec to better support for runner groups.

This requires to configure github auth in the webhook server, to keep backwards compatibility if github auth is not provided to the webhook server, this will assume all runner groups have no selected visibility and it will target any available runner group as before
This commit is contained in:
Felipe Galindo Sanchez
2021-12-19 01:29:44 -08:00
committed by GitHub
parent 0c34196d87
commit 4ebec38208
4 changed files with 341 additions and 82 deletions

View File

@@ -103,7 +103,7 @@ func (c *Client) GetRegistrationToken(ctx context.Context, enterprise, org, repo
return rt, nil
}
enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
enterprise, owner, repo, err := getEnterpriseOrganizationAndRepo(enterprise, org, repo)
if err != nil {
return rt, err
@@ -129,7 +129,7 @@ func (c *Client) GetRegistrationToken(ctx context.Context, enterprise, org, repo
// RemoveRunner removes a runner with specified runner ID from repository.
func (c *Client) RemoveRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) error {
enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
enterprise, owner, repo, err := getEnterpriseOrganizationAndRepo(enterprise, org, repo)
if err != nil {
return err
@@ -150,7 +150,7 @@ func (c *Client) RemoveRunner(ctx context.Context, enterprise, org, repo string,
// ListRunners returns a list of runners of specified owner/repository name.
func (c *Client) ListRunners(ctx context.Context, enterprise, org, repo string) ([]*github.Runner, error) {
enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
enterprise, owner, repo, err := getEnterpriseOrganizationAndRepo(enterprise, org, repo)
if err != nil {
return nil, err
@@ -176,6 +176,93 @@ func (c *Client) ListRunners(ctx context.Context, enterprise, org, repo string)
return runners, nil
}
func (c *Client) GetRunnerGroupsFromRepository(ctx context.Context, org, repo string, potentialEnterpriseGroups []string, potentialOrgGroups []string) ([]string, []string, error) {
var enterpriseRunnerGroups []string
var orgRunnerGroups []string
if org != "" {
runnerGroups, err := c.getOrganizationRunnerGroups(ctx, org, repo)
if err != nil {
return enterpriseRunnerGroups, orgRunnerGroups, err
}
for _, runnerGroup := range runnerGroups {
if runnerGroup.GetInherited() { // enterprise runner groups
if !containsString(potentialEnterpriseGroups, runnerGroup.GetName()) {
continue
}
if runnerGroup.GetVisibility() == "all" {
enterpriseRunnerGroups = append(enterpriseRunnerGroups, runnerGroup.GetName())
} else {
hasAccess, err := c.hasRepoAccessToOrganizationRunnerGroup(ctx, org, runnerGroup.GetID(), repo)
if err != nil {
return enterpriseRunnerGroups, orgRunnerGroups, err
}
if hasAccess {
enterpriseRunnerGroups = append(enterpriseRunnerGroups, runnerGroup.GetName())
}
}
} else { // organization runner groups
if !containsString(potentialOrgGroups, runnerGroup.GetName()) {
continue
}
if runnerGroup.GetVisibility() == "all" {
orgRunnerGroups = append(orgRunnerGroups, runnerGroup.GetName())
} else {
hasAccess, err := c.hasRepoAccessToOrganizationRunnerGroup(ctx, org, runnerGroup.GetID(), repo)
if err != nil {
return enterpriseRunnerGroups, orgRunnerGroups, err
}
if hasAccess {
orgRunnerGroups = append(orgRunnerGroups, runnerGroup.GetName())
}
}
}
}
}
return enterpriseRunnerGroups, orgRunnerGroups, nil
}
func (c *Client) hasRepoAccessToOrganizationRunnerGroup(ctx context.Context, org string, runnerGroupId int64, repo string) (bool, error) {
opts := github.ListOptions{PerPage: 100}
for {
list, res, err := c.Client.Actions.ListRepositoryAccessRunnerGroup(ctx, org, runnerGroupId, &opts)
if err != nil {
return false, fmt.Errorf("failed to list repository access for runner group: %w", err)
}
for _, githubRepo := range list.Repositories {
if githubRepo.GetFullName() == repo {
return true, nil
}
}
if res.NextPage == 0 {
break
}
opts.Page = res.NextPage
}
return false, nil
}
func (c *Client) getOrganizationRunnerGroups(ctx context.Context, org, repo string) ([]*github.RunnerGroup, error) {
var runnerGroups []*github.RunnerGroup
opts := github.ListOptions{PerPage: 100}
for {
list, res, err := c.Client.Actions.ListOrganizationRunnerGroups(ctx, org, &opts)
if err != nil {
return runnerGroups, fmt.Errorf("failed to list organization runner groups: %w", err)
}
runnerGroups = append(runnerGroups, list.RunnerGroups...)
if res.NextPage == 0 {
break
}
opts.Page = res.NextPage
}
return runnerGroups, nil
}
// cleanup removes expired registration tokens.
func (c *Client) cleanup() {
c.mu.Lock()
@@ -267,8 +354,8 @@ func (c *Client) listRepositoryWorkflowRuns(ctx context.Context, user string, re
return workflowRuns, nil
}
// Validates enterprise, organisation and repo arguments. Both are optional, but at least one should be specified
func getEnterpriseOrganisationAndRepo(enterprise, org, repo string) (string, string, string, error) {
// Validates enterprise, organization and repo arguments. Both are optional, but at least one should be specified
func getEnterpriseOrganizationAndRepo(enterprise, org, repo string) (string, string, string, error) {
if len(repo) > 0 {
owner, repository, err := splitOwnerAndRepo(repo)
return "", owner, repository, err
@@ -345,3 +432,12 @@ func (r *Client) IsRunnerBusy(ctx context.Context, enterprise, org, repo, name s
return false, &RunnerNotFound{runnerName: name}
}
func containsString(list []string, value string) bool {
for _, item := range list {
if item == value {
return true
}
}
return false
}