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:
parent
0c34196d87
commit
4ebec38208
|
|
@ -54,6 +54,32 @@ spec:
|
|||
key: github_webhook_secret_token
|
||||
name: {{ include "actions-runner-controller-github-webhook-server.secretName" . }}
|
||||
optional: true
|
||||
{{- if .Values.authSecret.enabled }}
|
||||
- name: GITHUB_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: github_token
|
||||
name: {{ include "actions-runner-controller.secretName" . }}
|
||||
optional: true
|
||||
- name: GITHUB_APP_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: github_app_id
|
||||
name: {{ include "actions-runner-controller.secretName" . }}
|
||||
optional: true
|
||||
- name: GITHUB_APP_INSTALLATION_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: github_app_installation_id
|
||||
name: {{ include "actions-runner-controller.secretName" . }}
|
||||
optional: true
|
||||
- name: GITHUB_APP_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: github_app_private_key
|
||||
name: {{ include "actions-runner-controller.secretName" . }}
|
||||
optional: true
|
||||
{{- end }}
|
||||
{{- range $key, $val := .Values.githubWebhookServer.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $val | quote }}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import (
|
|||
|
||||
actionsv1alpha1 "github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/actions-runner-controller/actions-runner-controller/controllers"
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
zaplib "go.uber.org/zap"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
|
|
@ -76,8 +78,17 @@ func main() {
|
|||
enableLeaderElection bool
|
||||
syncPeriod time.Duration
|
||||
logLevel string
|
||||
|
||||
ghClient *github.Client
|
||||
)
|
||||
|
||||
var c github.Config
|
||||
err = envconfig.Process("github", &c)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: processing environment variables: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
webhookSecretTokenEnv = os.Getenv(webhookSecretTokenEnvName)
|
||||
|
||||
flag.StringVar(&webhookAddr, "webhook-addr", ":8000", "The address the metric endpoint binds to.")
|
||||
|
|
@ -88,6 +99,11 @@ func main() {
|
|||
flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled. When you use autoscaling, set to a lower value like 10 minute, because this corresponds to the minimum time to react on demand change")
|
||||
flag.StringVar(&logLevel, "log-level", logLevelDebug, `The verbosity of the logging. Valid values are "debug", "info", "warn", "error". Defaults to "debug".`)
|
||||
flag.StringVar(&webhookSecretToken, "github-webhook-secret-token", "", "The personal access token of GitHub.")
|
||||
flag.StringVar(&c.Token, "github-token", c.Token, "The personal access token of GitHub.")
|
||||
flag.Int64Var(&c.AppID, "github-app-id", c.AppID, "The application ID of GitHub App.")
|
||||
flag.Int64Var(&c.AppInstallationID, "github-app-installation-id", c.AppInstallationID, "The installation ID of GitHub App.")
|
||||
flag.StringVar(&c.AppPrivateKey, "github-app-private-key", c.AppPrivateKey, "The path of a private key file to authenticate as a GitHub App")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if webhookSecretToken == "" && webhookSecretTokenEnv != "" {
|
||||
|
|
@ -121,6 +137,15 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
if len(c.Token) > 0 || (c.AppID > 0 && c.AppInstallationID > 0 && c.AppPrivateKey != "") {
|
||||
ghClient, err = c.NewClient()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: Client creation failed.", err)
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Runner")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.SetLogger(logger)
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
|
|
@ -143,6 +168,7 @@ func main() {
|
|||
Scheme: mgr.GetScheme(),
|
||||
SecretKeyBytes: []byte(webhookSecretToken),
|
||||
Namespace: watchNamespace,
|
||||
GitHubClient: ghClient,
|
||||
}
|
||||
|
||||
if err = hraGitHubWebhook.SetupWithManager(mgr); err != nil {
|
||||
|
|
|
|||
|
|
@ -37,12 +37,14 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
)
|
||||
|
||||
const (
|
||||
scaleTargetKey = "scaleTarget"
|
||||
|
||||
keyPrefixEnterprise = "enterprises/"
|
||||
keyRunnerGroup = "/group/"
|
||||
)
|
||||
|
||||
// HorizontalRunnerAutoscalerGitHubWebhook autoscales a HorizontalRunnerAutoscaler and the RunnerDeployment on each
|
||||
|
|
@ -57,6 +59,9 @@ type HorizontalRunnerAutoscalerGitHubWebhook struct {
|
|||
// the administrator is generated and specified in GitHub Web UI.
|
||||
SecretKeyBytes []byte
|
||||
|
||||
// GitHub Client to discover runner groups assigned to a repository
|
||||
GitHubClient *github.Client
|
||||
|
||||
// Namespace is the namespace to watch for HorizontalRunnerAutoscaler's to be
|
||||
// scaled on Webhook.
|
||||
// Set to empty for letting it watch for all namespaces.
|
||||
|
|
@ -436,63 +441,30 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleTarget(ctx co
|
|||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTarget(ctx context.Context, log logr.Logger, repo, owner, ownerType, enterprise string, f func(v1alpha1.ScaleUpTrigger) bool) (*ScaleTarget, error) {
|
||||
repositoryRunnerKey := owner + "/" + repo
|
||||
|
||||
if target, err := autoscaler.getScaleTarget(ctx, repositoryRunnerKey, f); err != nil {
|
||||
log.Info("finding repository-wide runner", "repository", repositoryRunnerKey)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info("scale up target is repository-wide runners", "repository", repo)
|
||||
return target, nil
|
||||
scaleTarget := func(value string) (*ScaleTarget, error) {
|
||||
return autoscaler.getScaleTarget(ctx, value, f)
|
||||
}
|
||||
|
||||
if ownerType == "User" {
|
||||
log.V(1).Info("no repository runner found", "organization", owner)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if target, err := autoscaler.getScaleTarget(ctx, owner, f); err != nil {
|
||||
log.Info("finding organizational runner", "organization", owner)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info("scale up target is organizational runners", "organization", owner)
|
||||
return target, nil
|
||||
}
|
||||
|
||||
if enterprise == "" {
|
||||
log.V(1).Info("no repository runner or organizational runner found",
|
||||
"repository", repositoryRunnerKey,
|
||||
"organization", owner,
|
||||
)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if target, err := autoscaler.getScaleTarget(ctx, enterpriseKey(enterprise), f); err != nil {
|
||||
log.Error(err, "finding enterprise runner", "enterprise", enterprise)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info("scale up target is enterprise runners", "enterprise", enterprise)
|
||||
return target, nil
|
||||
} else {
|
||||
log.V(1).Info("no repository/organizational/enterprise runner found",
|
||||
"repository", repositoryRunnerKey,
|
||||
"organization", owner,
|
||||
"enterprises", enterprise,
|
||||
)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return autoscaler.getScaleUpTargetWithFunction(ctx, log, repo, owner, ownerType, enterprise, scaleTarget)
|
||||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getJobScaleUpTargetForRepoOrOrg(
|
||||
ctx context.Context, log logr.Logger, repo, owner, ownerType, enterprise string, labels []string,
|
||||
) (*ScaleTarget, error) {
|
||||
|
||||
scaleTarget := func(value string) (*ScaleTarget, error) {
|
||||
return autoscaler.getJobScaleTarget(ctx, value, labels)
|
||||
}
|
||||
return autoscaler.getScaleUpTargetWithFunction(ctx, log, repo, owner, ownerType, enterprise, scaleTarget)
|
||||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTargetWithFunction(
|
||||
ctx context.Context, log logr.Logger, repo, owner, ownerType, enterprise string, scaleTarget func(value string) (*ScaleTarget, error)) (*ScaleTarget, error) {
|
||||
|
||||
repositoryRunnerKey := owner + "/" + repo
|
||||
|
||||
if target, err := autoscaler.getJobScaleTarget(ctx, repositoryRunnerKey, labels); err != nil {
|
||||
log.Info("finding repository-wide runner", "repository", repositoryRunnerKey)
|
||||
// Search for repository HRAs
|
||||
if target, err := scaleTarget(repositoryRunnerKey); err != nil {
|
||||
log.Error(err, "finding repository-wide runner", "repository", repositoryRunnerKey)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info("job scale up target is repository-wide runners", "repository", repo)
|
||||
|
|
@ -500,34 +472,41 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getJobScaleUpTargetFo
|
|||
}
|
||||
|
||||
if ownerType == "User" {
|
||||
log.V(1).Info("no repository runner found", "organization", owner)
|
||||
|
||||
log.V(1).Info("user repositories not supported", "owner", owner)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if target, err := autoscaler.getJobScaleTarget(ctx, owner, labels); err != nil {
|
||||
log.Info("finding organizational runner", "organization", owner)
|
||||
// Search for organization runner HRAs in default runner group
|
||||
if target, err := scaleTarget(owner); err != nil {
|
||||
log.Error(err, "finding organizational runner", "organization", owner)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info("job scale up target is organizational runners", "organization", owner)
|
||||
return target, nil
|
||||
}
|
||||
|
||||
if enterprise == "" {
|
||||
log.V(1).Info("no repository runner or organizational runner found",
|
||||
"repository", repositoryRunnerKey,
|
||||
"organization", owner,
|
||||
)
|
||||
return nil, nil
|
||||
if enterprise != "" {
|
||||
// Search for enterprise runner HRAs in default runner group
|
||||
if target, err := scaleTarget(enterpriseKey(enterprise)); err != nil {
|
||||
log.Error(err, "finding enterprise runner", "enterprise", enterprise)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info("scale up target is default enterprise runners", "enterprise", enterprise)
|
||||
return target, nil
|
||||
}
|
||||
}
|
||||
|
||||
if target, err := autoscaler.getJobScaleTarget(ctx, enterpriseKey(enterprise), labels); err != nil {
|
||||
log.Error(err, "finding enterprise runner", "enterprise", enterprise)
|
||||
// At this point there were no default organization/enterprise runners available to use, try now
|
||||
// searching in runner groups
|
||||
|
||||
// We need to get the potential runner groups first to avoid spending API queries needless. Once/if GitHub improves an
|
||||
// API to find related/linked runner groups from a specific repository this logic could be removed
|
||||
availableEnterpriseGroups, availableOrganizationGroups, err := autoscaler.getPotentialGroupsFromHRAs(ctx, enterprise, owner)
|
||||
if err != nil {
|
||||
log.Error(err, "finding potential organization runner groups from HRAs", "organization", owner)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info("scale up target is enterprise runners", "enterprise", enterprise)
|
||||
return target, nil
|
||||
} else {
|
||||
}
|
||||
if len(availableEnterpriseGroups) == 0 && len(availableOrganizationGroups) == 0 {
|
||||
log.V(1).Info("no repository/organizational/enterprise runner found",
|
||||
"repository", repositoryRunnerKey,
|
||||
"organization", owner,
|
||||
|
|
@ -535,9 +514,103 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getJobScaleUpTargetFo
|
|||
)
|
||||
}
|
||||
|
||||
var enterpriseGroups []string
|
||||
var organizationGroups []string
|
||||
if autoscaler.GitHubClient != nil {
|
||||
// Get available organization runner groups and enterprise runner groups for a repository
|
||||
// These are the sum of runner groups with repository access = All repositories plus
|
||||
// runner groups where owner/repo has access to
|
||||
enterpriseGroups, organizationGroups, err = autoscaler.GitHubClient.GetRunnerGroupsFromRepository(ctx, owner, repositoryRunnerKey, availableEnterpriseGroups, availableOrganizationGroups)
|
||||
log.V(1).Info("Searching in runner groups", "enterprise.groups", enterpriseGroups, "organization.groups", organizationGroups)
|
||||
if err != nil {
|
||||
log.Error(err, "Unable to find runner groups from repository", "organization", owner, "repository", repo)
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
// For backwards compatibility if GitHub authentication is not configured, we assume all runner groups have
|
||||
// visibility=all to honor the previous implementation, therefore any available enterprise/organization runner
|
||||
// is a potential target for scaling
|
||||
enterpriseGroups = availableEnterpriseGroups
|
||||
organizationGroups = availableOrganizationGroups
|
||||
}
|
||||
|
||||
for _, group := range organizationGroups {
|
||||
if target, err := scaleTarget(organizationalRunnerGroupKey(owner, group)); err != nil {
|
||||
log.Error(err, "finding organizational runner group", "organization", owner)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info(fmt.Sprintf("job scale up target is organizational runner group %s", target.Name), "organization", owner)
|
||||
return target, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, group := range enterpriseGroups {
|
||||
if target, err := scaleTarget(enterpriseRunnerGroupKey(enterprise, group)); err != nil {
|
||||
log.Error(err, "finding enterprise runner group", "enterprise", owner)
|
||||
return nil, err
|
||||
} else if target != nil {
|
||||
log.Info(fmt.Sprintf("job scale up target is enterprise runner group %s", target.Name), "enterprise", owner)
|
||||
return target, nil
|
||||
}
|
||||
}
|
||||
|
||||
log.V(1).Info("no repository/organizational/enterprise runner found",
|
||||
"repository", repositoryRunnerKey,
|
||||
"organization", owner,
|
||||
"enterprises", enterprise,
|
||||
)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getPotentialGroupsFromHRAs(ctx context.Context, enterprise, org string) ([]string, []string, error) {
|
||||
var enterpriseRunnerGroups []string
|
||||
var orgRunnerGroups []string
|
||||
ns := autoscaler.Namespace
|
||||
|
||||
var defaultListOpts []client.ListOption
|
||||
if ns != "" {
|
||||
defaultListOpts = append(defaultListOpts, client.InNamespace(ns))
|
||||
}
|
||||
|
||||
opts := append([]client.ListOption{}, defaultListOpts...)
|
||||
if autoscaler.Namespace != "" {
|
||||
opts = append(opts, client.InNamespace(autoscaler.Namespace))
|
||||
}
|
||||
|
||||
var hraList v1alpha1.HorizontalRunnerAutoscalerList
|
||||
if err := autoscaler.List(ctx, &hraList, opts...); err != nil {
|
||||
return orgRunnerGroups, enterpriseRunnerGroups, err
|
||||
}
|
||||
|
||||
for _, hra := range hraList.Items {
|
||||
switch hra.Spec.ScaleTargetRef.Kind {
|
||||
case "RunnerSet":
|
||||
var rs v1alpha1.RunnerSet
|
||||
if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil {
|
||||
return orgRunnerGroups, enterpriseRunnerGroups, err
|
||||
}
|
||||
if rs.Spec.Organization == org && rs.Spec.Group != "" {
|
||||
orgRunnerGroups = append(orgRunnerGroups, rs.Spec.Group)
|
||||
}
|
||||
if rs.Spec.Enterprise == enterprise && rs.Spec.Group != "" {
|
||||
enterpriseRunnerGroups = append(enterpriseRunnerGroups, rs.Spec.Group)
|
||||
}
|
||||
case "RunnerDeployment", "":
|
||||
var rd v1alpha1.RunnerDeployment
|
||||
if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil {
|
||||
return orgRunnerGroups, enterpriseRunnerGroups, err
|
||||
}
|
||||
if rd.Spec.Template.Spec.Organization == org && rd.Spec.Template.Spec.Group != "" {
|
||||
orgRunnerGroups = append(orgRunnerGroups, rd.Spec.Template.Spec.Group)
|
||||
}
|
||||
if rd.Spec.Template.Spec.Enterprise == enterprise && rd.Spec.Template.Spec.Group != "" {
|
||||
enterpriseRunnerGroups = append(enterpriseRunnerGroups, rd.Spec.Template.Spec.Group)
|
||||
}
|
||||
}
|
||||
}
|
||||
return enterpriseRunnerGroups, orgRunnerGroups, nil
|
||||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getJobScaleTarget(ctx context.Context, name string, labels []string) (*ScaleTarget, error) {
|
||||
hras, err := autoscaler.findHRAsByKey(ctx, name)
|
||||
if err != nil {
|
||||
|
|
@ -582,6 +655,9 @@ HRA:
|
|||
|
||||
// Ensure that the RunnerSet-managed runners have all the labels requested by the workflow_job.
|
||||
for _, l := range labels {
|
||||
if l == "self-hosted" {
|
||||
continue // label is automatically added to self-hosted runners
|
||||
}
|
||||
var matched bool
|
||||
|
||||
// ignore "self-hosted" label as all instance here are self-hosted
|
||||
|
|
@ -613,6 +689,9 @@ HRA:
|
|||
|
||||
// Ensure that the RunnerDeployment-managed runners have all the labels requested by the workflow_job.
|
||||
for _, l := range labels {
|
||||
if l == "self-hosted" {
|
||||
continue // label is automatically added to self-hosted runners
|
||||
}
|
||||
var matched bool
|
||||
|
||||
// ignore "self-hosted" label as all instance here are self-hosted
|
||||
|
|
@ -724,31 +803,55 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) SetupWithManager(mgr
|
|||
switch hra.Spec.ScaleTargetRef.Kind {
|
||||
case "", "RunnerDeployment":
|
||||
var rd v1alpha1.RunnerDeployment
|
||||
|
||||
if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil {
|
||||
autoscaler.Log.V(1).Info(fmt.Sprintf("RunnerDeployment not found with scale target ref name %s for hra %s", hra.Spec.ScaleTargetRef.Name, hra.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := []string{rd.Spec.Template.Spec.Repository, rd.Spec.Template.Spec.Organization}
|
||||
|
||||
if enterprise := rd.Spec.Template.Spec.Enterprise; enterprise != "" {
|
||||
keys = append(keys, enterpriseKey(enterprise))
|
||||
keys := []string{}
|
||||
if rd.Spec.Template.Spec.Repository != "" {
|
||||
keys = append(keys, rd.Spec.Template.Spec.Repository) // Repository runners
|
||||
}
|
||||
|
||||
if rd.Spec.Template.Spec.Organization != "" {
|
||||
if group := rd.Spec.Template.Spec.Group; group != "" {
|
||||
keys = append(keys, organizationalRunnerGroupKey(rd.Spec.Template.Spec.Organization, rd.Spec.Template.Spec.Group)) // Organization runner groups
|
||||
} else {
|
||||
keys = append(keys, rd.Spec.Template.Spec.Organization) // Organization runners
|
||||
}
|
||||
}
|
||||
if enterprise := rd.Spec.Template.Spec.Enterprise; enterprise != "" {
|
||||
if group := rd.Spec.Template.Spec.Group; group != "" {
|
||||
keys = append(keys, enterpriseRunnerGroupKey(enterprise, rd.Spec.Template.Spec.Group)) // Enterprise runner groups
|
||||
} else {
|
||||
keys = append(keys, enterpriseKey(enterprise)) // Enterprise runners
|
||||
}
|
||||
}
|
||||
autoscaler.Log.V(1).Info(fmt.Sprintf("HRA keys indexed for HRA %s: %v", hra.Name, keys))
|
||||
return keys
|
||||
case "RunnerSet":
|
||||
var rs v1alpha1.RunnerSet
|
||||
|
||||
if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rs); err != nil {
|
||||
autoscaler.Log.V(1).Info(fmt.Sprintf("RunnerSet not found with scale target ref name %s for hra %s", hra.Spec.ScaleTargetRef.Name, hra.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := []string{rs.Spec.Repository, rs.Spec.Organization}
|
||||
|
||||
if enterprise := rs.Spec.Enterprise; enterprise != "" {
|
||||
keys = append(keys, enterpriseKey(enterprise))
|
||||
keys := []string{}
|
||||
if rs.Spec.Repository != "" {
|
||||
keys = append(keys, rs.Spec.Repository) // Repository runners
|
||||
}
|
||||
|
||||
if rs.Spec.Organization != "" {
|
||||
keys = append(keys, rs.Spec.Organization) // Organization runners
|
||||
if group := rs.Spec.Group; group != "" {
|
||||
keys = append(keys, organizationalRunnerGroupKey(rs.Spec.Organization, rs.Spec.Group)) // Organization runner groups
|
||||
}
|
||||
}
|
||||
if enterprise := rs.Spec.Enterprise; enterprise != "" {
|
||||
keys = append(keys, enterpriseKey(enterprise)) // Enterprise runners
|
||||
if group := rs.Spec.Group; group != "" {
|
||||
keys = append(keys, enterpriseRunnerGroupKey(enterprise, rs.Spec.Group)) // Enterprise runner groups
|
||||
}
|
||||
}
|
||||
autoscaler.Log.V(1).Info(fmt.Sprintf("HRA keys indexed for HRA %s: %v", hra.Name, keys))
|
||||
return keys
|
||||
}
|
||||
|
||||
|
|
@ -766,3 +869,11 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) SetupWithManager(mgr
|
|||
func enterpriseKey(name string) string {
|
||||
return keyPrefixEnterprise + name
|
||||
}
|
||||
|
||||
func organizationalRunnerGroupKey(owner, group string) string {
|
||||
return owner + keyRunnerGroup + group
|
||||
}
|
||||
|
||||
func enterpriseRunnerGroupKey(enterprise, group string) string {
|
||||
return keyPrefixEnterprise + enterprise + keyRunnerGroup + group
|
||||
}
|
||||
|
|
|
|||
106
github/github.go
106
github/github.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue