Add support for enterprise runners (#290)

* Add support for enterprise runners

* update docs
This commit is contained in:
Jesse Haka 2021-02-05 02:31:06 +02:00 committed by GitHub
parent 831db9ee2a
commit 28e80a2d28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 207 additions and 102 deletions

View File

@ -32,13 +32,53 @@ helm upgrade --install -n actions-runner-system actions-runner-controller/action
### Github Enterprise support ### Github Enterprise support
If you use either Github Enterprise Cloud or Server (and have recent enought version supporting Actions), you can use **actions-runner-controller** with those, too. Authentication works same way as with public Github (repo and organization level). If you use either Github Enterprise Cloud or Server, you can use **actions-runner-controller** with those, too.
Authentication works same way as with public Github (repo and organization level).
The minimum version of Github Enterprise Server is 3.0.0 (or rc1/rc2).
In most cases maintainers do not have environment where to test changes and are reliant on the community for testing.
```shell ```shell
kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> --namespace actions-runner-system kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> --namespace actions-runner-system
``` ```
[Enterprise level](https://docs.github.com/en/enterprise-server@2.22/actions/hosting-your-own-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-an-enterprise) runners are not working yet as there's no API definition for those. #### Enterprise runners usage
In order to use enterprise runners you must have Admin access to Github Enterprise and you should do Personal Access Token (PAT)
with `enterprise:admin` access. Enterprise runners are not possible to run with Github APP or any other permission.
When you use enterprise runners those will get access to Github Organisations. However, access to the repositories is **NOT**
allowed by default. Each Github Organisation must allow Enterprise runner groups to be used in repositories.
This is needed only one time and is permanent after that.
Example:
```yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: ghe-runner-deployment
spec:
replicas: 2
template:
spec:
enterprise: your-enterprise-name
dockerdWithinRunnerContainer: true
resources:
limits:
cpu: "4000m"
memory: "2Gi"
requests:
cpu: "200m"
memory: "200Mi"
volumeMounts:
- mountPath: /runner
name: runner
volumes:
- name: runner
emptyDir: {}
```
## Setting up authentication with GitHub API ## Setting up authentication with GitHub API

View File

@ -25,6 +25,10 @@ import (
// RunnerSpec defines the desired state of Runner // RunnerSpec defines the desired state of Runner
type RunnerSpec struct { type RunnerSpec struct {
// +optional
// +kubebuilder:validation:Pattern=`^[^/]+$`
Enterprise string `json:"enterprise,omitempty"`
// +optional // +optional
// +kubebuilder:validation:Pattern=`^[^/]+$` // +kubebuilder:validation:Pattern=`^[^/]+$`
Organization string `json:"organization,omitempty"` Organization string `json:"organization,omitempty"`
@ -92,12 +96,22 @@ type RunnerSpec struct {
// ValidateRepository validates repository field. // ValidateRepository validates repository field.
func (rs *RunnerSpec) ValidateRepository() error { func (rs *RunnerSpec) ValidateRepository() error {
// Organization and repository are both exclusive. // Enterprise, Organization and repository are both exclusive.
if len(rs.Organization) == 0 && len(rs.Repository) == 0 { foundCount := 0
return errors.New("Spec needs organization or repository") if len(rs.Organization) > 0 {
foundCount += 1
} }
if len(rs.Organization) > 0 && len(rs.Repository) > 0 { if len(rs.Repository) > 0 {
return errors.New("Spec cannot have both organization and repository") foundCount += 1
}
if len(rs.Enterprise) > 0 {
foundCount += 1
}
if foundCount == 0 {
return errors.New("Spec needs enterprise, organization or repository")
}
if foundCount > 1 {
return errors.New("Spec cannot have many fields defined enterprise, organization and repository")
} }
return nil return nil
@ -113,6 +127,7 @@ type RunnerStatus struct {
// RunnerStatusRegistration contains runner registration status // RunnerStatusRegistration contains runner registration status
type RunnerStatusRegistration struct { type RunnerStatusRegistration struct {
Enterprise string `json:"enterprise,omitempty"`
Organization string `json:"organization,omitempty"` Organization string `json:"organization,omitempty"`
Repository string `json:"repository,omitempty"` Repository string `json:"repository,omitempty"`
Labels []string `json:"labels,omitempty"` Labels []string `json:"labels,omitempty"`
@ -122,6 +137,7 @@ type RunnerStatusRegistration struct {
// +kubebuilder:object:root=true // +kubebuilder:object:root=true
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
// +kubebuilder:printcolumn:JSONPath=".spec.enterprise",name=Enterprise,type=string
// +kubebuilder:printcolumn:JSONPath=".spec.organization",name=Organization,type=string // +kubebuilder:printcolumn:JSONPath=".spec.organization",name=Organization,type=string
// +kubebuilder:printcolumn:JSONPath=".spec.repository",name=Repository,type=string // +kubebuilder:printcolumn:JSONPath=".spec.repository",name=Repository,type=string
// +kubebuilder:printcolumn:JSONPath=".spec.labels",name=Labels,type=string // +kubebuilder:printcolumn:JSONPath=".spec.labels",name=Labels,type=string

View File

@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@ -7,6 +7,9 @@ metadata:
name: runners.actions.summerwind.dev name: runners.actions.summerwind.dev
spec: spec:
additionalPrinterColumns: additionalPrinterColumns:
- JSONPath: .spec.enterprise
name: Enterprise
type: string
- JSONPath: .spec.organization - JSONPath: .spec.organization
name: Organization name: Organization
type: string type: string
@ -419,6 +422,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.
@ -1541,6 +1547,8 @@ spec:
registration: registration:
description: RunnerStatusRegistration contains runner registration status description: RunnerStatusRegistration contains runner registration status
properties: properties:
enterprise:
type: string
expiresAt: expiresAt:
format: date-time format: date-time
type: string type: string

View File

@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@ -7,6 +7,9 @@ metadata:
name: runners.actions.summerwind.dev name: runners.actions.summerwind.dev
spec: spec:
additionalPrinterColumns: additionalPrinterColumns:
- JSONPath: .spec.enterprise
name: Enterprise
type: string
- JSONPath: .spec.organization - JSONPath: .spec.organization
name: Organization name: Organization
type: string type: string
@ -419,6 +422,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.
@ -1541,6 +1547,8 @@ spec:
registration: registration:
description: RunnerStatusRegistration contains runner registration status description: RunnerStatusRegistration contains runner registration status
properties: properties:
enterprise:
type: string
expiresAt: expiresAt:
format: date-time format: date-time
type: string type: string

View File

@ -204,7 +204,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByPercentageRunn
} }
// ListRunners will return all runners managed by GitHub - not restricted to ns // ListRunners will return all runners managed by GitHub - not restricted to ns
runners, err := r.GitHubClient.ListRunners(ctx, orgName, "") runners, err := r.GitHubClient.ListRunners(ctx, "", orgName, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -95,7 +95,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if removed { if removed {
if len(runner.Status.Registration.Token) > 0 { if len(runner.Status.Registration.Token) > 0 {
ok, err := r.unregisterRunner(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) ok, err := r.unregisterRunner(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
log.Error(err, "Failed to unregister runner") log.Error(err, "Failed to unregister runner")
return ctrl.Result{}, err return ctrl.Result{}, err
@ -194,7 +194,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, err return ctrl.Result{}, err
} }
runnerBusy, err := r.isRunnerBusy(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) runnerBusy, err := r.isRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
log.Error(err, "Failed to check if runner is busy") log.Error(err, "Failed to check if runner is busy")
return ctrl.Result{}, nil return ctrl.Result{}, nil
@ -227,8 +227,8 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
func (r *RunnerReconciler) isRunnerBusy(ctx context.Context, org, repo, name string) (bool, error) { func (r *RunnerReconciler) isRunnerBusy(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
runners, err := r.GitHubClient.ListRunners(ctx, org, repo) runners, err := r.GitHubClient.ListRunners(ctx, enterprise, org, repo)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -242,8 +242,8 @@ func (r *RunnerReconciler) isRunnerBusy(ctx context.Context, org, repo, name str
return false, fmt.Errorf("runner not found") return false, fmt.Errorf("runner not found")
} }
func (r *RunnerReconciler) unregisterRunner(ctx context.Context, org, repo, name string) (bool, error) { func (r *RunnerReconciler) unregisterRunner(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
runners, err := r.GitHubClient.ListRunners(ctx, org, repo) runners, err := r.GitHubClient.ListRunners(ctx, enterprise, org, repo)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -263,7 +263,7 @@ func (r *RunnerReconciler) unregisterRunner(ctx context.Context, org, repo, name
return false, nil return false, nil
} }
if err := r.GitHubClient.RemoveRunner(ctx, org, repo, id); err != nil { if err := r.GitHubClient.RemoveRunner(ctx, enterprise, org, repo, id); err != nil {
return false, err return false, err
} }
@ -277,7 +277,7 @@ func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v
log := r.Log.WithValues("runner", runner.Name) log := r.Log.WithValues("runner", runner.Name)
rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed") r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed")
log.Error(err, "Failed to get new registration token") log.Error(err, "Failed to get new registration token")
@ -339,6 +339,10 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
Name: "RUNNER_REPO", Name: "RUNNER_REPO",
Value: runner.Spec.Repository, Value: runner.Spec.Repository,
}, },
{
Name: "RUNNER_ENTERPRISE",
Value: runner.Spec.Enterprise,
},
{ {
Name: "RUNNER_LABELS", Name: "RUNNER_LABELS",
Value: strings.Join(runner.Spec.Labels, ","), Value: strings.Join(runner.Spec.Labels, ","),

View File

@ -102,7 +102,7 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
// get runners that are currently not busy // get runners that are currently not busy
var notBusy []v1alpha1.Runner var notBusy []v1alpha1.Runner
for _, runner := range myRunners { for _, runner := range myRunners {
busy, err := r.isRunnerBusy(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) busy, err := r.isRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
log.Error(err, "Failed to check if runner is busy") log.Error(err, "Failed to check if runner is busy")
return ctrl.Result{}, err return ctrl.Result{}, err
@ -187,8 +187,8 @@ func (r *RunnerReplicaSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r) Complete(r)
} }
func (r *RunnerReplicaSetReconciler) isRunnerBusy(ctx context.Context, org, repo, name string) (bool, error) { func (r *RunnerReplicaSetReconciler) isRunnerBusy(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
runners, err := r.GitHubClient.ListRunners(ctx, org, repo) runners, err := r.GitHubClient.ListRunners(ctx, enterprise, org, repo)
r.Log.Info("runners", "github", runners) r.Log.Info("runners", "github", runners)
if err != nil { if err != nil {
return false, err return false, err

View File

@ -78,7 +78,7 @@ func (c *Config) NewClient() (*Client, error) {
} }
// GetRegistrationToken returns a registration token tied with the name of repository and runner. // GetRegistrationToken returns a registration token tied with the name of repository and runner.
func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name string) (*github.RegistrationToken, error) { func (c *Client) GetRegistrationToken(ctx context.Context, enterprise, org, repo, name string) (*github.RegistrationToken, error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -89,13 +89,13 @@ func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name strin
return rt, nil return rt, nil
} }
owner, repo, err := getOwnerAndRepo(org, repo) enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
if err != nil { if err != nil {
return rt, err return rt, err
} }
rt, res, err := c.createRegistrationToken(ctx, owner, repo) rt, res, err := c.createRegistrationToken(ctx, enterprise, owner, repo)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create registration token: %v", err) return nil, fmt.Errorf("failed to create registration token: %v", err)
@ -114,14 +114,14 @@ func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name strin
} }
// RemoveRunner removes a runner with specified runner ID from repository. // RemoveRunner removes a runner with specified runner ID from repository.
func (c *Client) RemoveRunner(ctx context.Context, org, repo string, runnerID int64) error { func (c *Client) RemoveRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) error {
owner, repo, err := getOwnerAndRepo(org, repo) enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
if err != nil { if err != nil {
return err return err
} }
res, err := c.removeRunner(ctx, owner, repo, runnerID) res, err := c.removeRunner(ctx, enterprise, owner, repo, runnerID)
if err != nil { if err != nil {
return fmt.Errorf("failed to remove runner: %v", err) return fmt.Errorf("failed to remove runner: %v", err)
@ -135,8 +135,8 @@ func (c *Client) RemoveRunner(ctx context.Context, org, repo string, runnerID in
} }
// ListRunners returns a list of runners of specified owner/repository name. // ListRunners returns a list of runners of specified owner/repository name.
func (c *Client) ListRunners(ctx context.Context, org, repo string) ([]*github.Runner, error) { func (c *Client) ListRunners(ctx context.Context, enterprise, org, repo string) ([]*github.Runner, error) {
owner, repo, err := getOwnerAndRepo(org, repo) enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -146,7 +146,7 @@ func (c *Client) ListRunners(ctx context.Context, org, repo string) ([]*github.R
opts := github.ListOptions{PerPage: 10} opts := github.ListOptions{PerPage: 10}
for { for {
list, res, err := c.listRunners(ctx, owner, repo, &opts) list, res, err := c.listRunners(ctx, enterprise, owner, repo, &opts)
if err != nil { if err != nil {
return runners, fmt.Errorf("failed to list runners: %v", err) return runners, fmt.Errorf("failed to list runners: %v", err)
@ -174,42 +174,52 @@ func (c *Client) cleanup() {
} }
} }
// wrappers for github functions (switch between organization/repository mode) // wrappers for github functions (switch between enterprise/organization/repository mode)
// so the calling functions don't need to switch and their code is a bit cleaner // so the calling functions don't need to switch and their code is a bit cleaner
func (c *Client) createRegistrationToken(ctx context.Context, owner, repo string) (*github.RegistrationToken, *github.Response, error) { func (c *Client) createRegistrationToken(ctx context.Context, enterprise, org, repo string) (*github.RegistrationToken, *github.Response, error) {
if len(repo) > 0 { if len(repo) > 0 {
return c.Client.Actions.CreateRegistrationToken(ctx, owner, repo) return c.Client.Actions.CreateRegistrationToken(ctx, org, repo)
}
return c.Client.Actions.CreateOrganizationRegistrationToken(ctx, owner)
}
func (c *Client) removeRunner(ctx context.Context, owner, repo string, runnerID int64) (*github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.RemoveRunner(ctx, owner, repo, runnerID)
}
return c.Client.Actions.RemoveOrganizationRunner(ctx, owner, runnerID)
}
func (c *Client) listRunners(ctx context.Context, owner, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.ListRunners(ctx, owner, repo, opts)
}
return c.Client.Actions.ListOrganizationRunners(ctx, owner, opts)
}
// Validates owner and repo arguments. Both are optional, but at least one should be specified
func getOwnerAndRepo(org, repo string) (string, string, error) {
if len(repo) > 0 {
return splitOwnerAndRepo(repo)
} }
if len(org) > 0 { if len(org) > 0 {
return org, "", nil return c.Client.Actions.CreateOrganizationRegistrationToken(ctx, org)
} }
return "", "", fmt.Errorf("organization and repository are both empty") return c.Client.Enterprise.CreateRegistrationToken(ctx, enterprise)
}
func (c *Client) removeRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) (*github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.RemoveRunner(ctx, org, repo, runnerID)
}
if len(org) > 0 {
return c.Client.Actions.RemoveOrganizationRunner(ctx, org, runnerID)
}
return c.Client.Enterprise.RemoveRunner(ctx, enterprise, runnerID)
}
func (c *Client) listRunners(ctx context.Context, enterprise, org, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.ListRunners(ctx, org, repo, opts)
}
if len(org) > 0 {
return c.Client.Actions.ListOrganizationRunners(ctx, org, opts)
}
return c.Client.Enterprise.ListRunners(ctx, enterprise, opts)
}
// 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) {
if len(repo) > 0 {
owner, repository, err := splitOwnerAndRepo(repo)
return "", owner, repository, err
}
if len(org) > 0 {
return "", org, "", nil
}
if len(enterprise) > 0 {
return enterprise, "", "", nil
}
return "", "", "", fmt.Errorf("enterprise, organization and repository are all empty")
} }
func getRegistrationKey(org, repo string) string { func getRegistrationKey(org, repo string) string {

View File

@ -39,22 +39,26 @@ func TestMain(m *testing.M) {
func TestGetRegistrationToken(t *testing.T) { func TestGetRegistrationToken(t *testing.T) {
tests := []struct { tests := []struct {
org string enterprise string
repo string org string
token string repo string
err bool token string
err bool
}{ }{
{org: "", repo: "test/valid", token: fake.RegistrationToken, err: false}, {enterprise: "", org: "", repo: "test/valid", token: fake.RegistrationToken, err: false},
{org: "", repo: "test/invalid", token: "", err: true}, {enterprise: "", org: "", repo: "test/invalid", token: "", err: true},
{org: "", repo: "test/error", token: "", err: true}, {enterprise: "", org: "", repo: "test/error", token: "", err: true},
{org: "test", repo: "", token: fake.RegistrationToken, err: false}, {enterprise: "", org: "test", repo: "", token: fake.RegistrationToken, err: false},
{org: "invalid", repo: "", token: "", err: true}, {enterprise: "", org: "invalid", repo: "", token: "", err: true},
{org: "error", repo: "", token: "", err: true}, {enterprise: "", org: "error", repo: "", token: "", err: true},
{enterprise: "test", org: "", repo: "", token: fake.RegistrationToken, err: false},
{enterprise: "invalid", org: "", repo: "", token: "", err: true},
{enterprise: "error", org: "", repo: "", token: "", err: true},
} }
client := newTestClient() client := newTestClient()
for i, tt := range tests { for i, tt := range tests {
rt, err := client.GetRegistrationToken(context.Background(), tt.org, tt.repo, "test") rt, err := client.GetRegistrationToken(context.Background(), tt.enterprise, tt.org, tt.repo, "test")
if !tt.err && err != nil { if !tt.err && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err) t.Errorf("[%d] unexpected error: %v", i, err)
} }
@ -66,22 +70,26 @@ func TestGetRegistrationToken(t *testing.T) {
func TestListRunners(t *testing.T) { func TestListRunners(t *testing.T) {
tests := []struct { tests := []struct {
org string enterprise string
repo string org string
length int repo string
err bool length int
err bool
}{ }{
{org: "", repo: "test/valid", length: 2, err: false}, {enterprise: "", org: "", repo: "test/valid", length: 2, err: false},
{org: "", repo: "test/invalid", length: 0, err: true}, {enterprise: "", org: "", repo: "test/invalid", length: 0, err: true},
{org: "", repo: "test/error", length: 0, err: true}, {enterprise: "", org: "", repo: "test/error", length: 0, err: true},
{org: "test", repo: "", length: 2, err: false}, {enterprise: "", org: "test", repo: "", length: 2, err: false},
{org: "invalid", repo: "", length: 0, err: true}, {enterprise: "", org: "invalid", repo: "", length: 0, err: true},
{org: "error", repo: "", length: 0, err: true}, {enterprise: "", org: "error", repo: "", length: 0, err: true},
{enterprise: "test", org: "", repo: "", length: 2, err: false},
{enterprise: "invalid", org: "", repo: "", length: 0, err: true},
{enterprise: "error", org: "", repo: "", length: 0, err: true},
} }
client := newTestClient() client := newTestClient()
for i, tt := range tests { for i, tt := range tests {
runners, err := client.ListRunners(context.Background(), tt.org, tt.repo) runners, err := client.ListRunners(context.Background(), tt.enterprise, tt.org, tt.repo)
if !tt.err && err != nil { if !tt.err && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err) t.Errorf("[%d] unexpected error: %v", i, err)
} }
@ -93,21 +101,25 @@ func TestListRunners(t *testing.T) {
func TestRemoveRunner(t *testing.T) { func TestRemoveRunner(t *testing.T) {
tests := []struct { tests := []struct {
org string enterprise string
repo string org string
err bool repo string
err bool
}{ }{
{org: "", repo: "test/valid", err: false}, {enterprise: "", org: "", repo: "test/valid", err: false},
{org: "", repo: "test/invalid", err: true}, {enterprise: "", org: "", repo: "test/invalid", err: true},
{org: "", repo: "test/error", err: true}, {enterprise: "", org: "", repo: "test/error", err: true},
{org: "test", repo: "", err: false}, {enterprise: "", org: "test", repo: "", err: false},
{org: "invalid", repo: "", err: true}, {enterprise: "", org: "invalid", repo: "", err: true},
{org: "error", repo: "", err: true}, {enterprise: "", org: "error", repo: "", err: true},
{enterprise: "test", org: "", repo: "", err: false},
{enterprise: "invalid", org: "", repo: "", err: true},
{enterprise: "error", org: "", repo: "", err: true},
} }
client := newTestClient() client := newTestClient()
for i, tt := range tests { for i, tt := range tests {
err := client.RemoveRunner(context.Background(), tt.org, tt.repo, int64(1)) err := client.RemoveRunner(context.Background(), tt.enterprise, tt.org, tt.repo, int64(1))
if !tt.err && err != nil { if !tt.err && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err) t.Errorf("[%d] unexpected error: %v", i, err)
} }

5
go.mod
View File

@ -6,10 +6,7 @@ require (
github.com/bradleyfalzon/ghinstallation v1.1.1 github.com/bradleyfalzon/ghinstallation v1.1.1
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/go-logr/logr v0.1.0 github.com/go-logr/logr v0.1.0
github.com/google/go-github v17.0.0+incompatible // indirect github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04
github.com/google/go-github/v33 v33.0.0
github.com/google/go-querystring v1.0.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/onsi/ginkgo v1.8.0 github.com/onsi/ginkgo v1.8.0

8
go.sum
View File

@ -116,14 +116,10 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04 h1:wEYk2h/GwOhImcVjiTIceP88WxVbXw2F+ARYUQMEsfg= github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a h1:Z9Nzq8ntvvXCLnFGOkzzcD8HDOzOo+obuwE5oK85vNQ=
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=

View File

@ -16,14 +16,16 @@ if [ -z "${RUNNER_NAME}" ]; then
exit 1 exit 1
fi fi
if [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_REPO}" ]; then if [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_REPO}" ] && [ -n "${RUNNER_ENTERPRISE}" ]; then
ATTACH="${RUNNER_ORG}/${RUNNER_REPO}" ATTACH="${RUNNER_ORG}/${RUNNER_REPO}"
elif [ -n "${RUNNER_ORG}" ]; then elif [ -n "${RUNNER_ORG}" ]; then
ATTACH="${RUNNER_ORG}" ATTACH="${RUNNER_ORG}"
elif [ -n "${RUNNER_REPO}" ]; then elif [ -n "${RUNNER_REPO}" ]; then
ATTACH="${RUNNER_REPO}" ATTACH="${RUNNER_REPO}"
elif [ -n "${RUNNER_ENTERPRISE}" ]; then
ATTACH="enterprises/${RUNNER_ENTERPRISE}"
else else
echo "At least one of RUNNER_ORG or RUNNER_REPO must be set" 1>&2 echo "At least one of RUNNER_ORG or RUNNER_REPO or RUNNER_ENTERPRISE must be set" 1>&2
exit 1 exit 1
fi fi