Add support for enterprise runners (#290)
* Add support for enterprise runners * update docs
This commit is contained in:
		
							parent
							
								
									831db9ee2a
								
							
						
					
					
						commit
						28e80a2d28
					
				
							
								
								
									
										44
									
								
								README.md
								
								
								
								
							
							
						
						
									
										44
									
								
								README.md
								
								
								
								
							|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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
 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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, ","), | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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
								
								
								
								
							
							
						
						
									
										5
									
								
								go.mod
								
								
								
								
							|  | @ -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
								
								
								
								
							
							
						
						
									
										8
									
								
								go.sum
								
								
								
								
							|  | @ -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= | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue