organization and repository are now exclusive
This commit is contained in:
		
							parent
							
								
									8c5b776807
								
							
						
					
					
						commit
						9f57f52e36
					
				|  | @ -23,12 +23,12 @@ import ( | ||||||
| 
 | 
 | ||||||
| // RunnerSpec defines the desired state of Runner
 | // RunnerSpec defines the desired state of Runner
 | ||||||
| type RunnerSpec struct { | type RunnerSpec struct { | ||||||
| 	// +kubebuilder:validation:MinLength=3
 | 	// +optional
 | ||||||
| 	// +kubebuilder:validation:Pattern=`^[^/]+$`
 | 	// +kubebuilder:validation:Pattern=`^[^/]+$`
 | ||||||
| 	Organization string `json:"organization"` | 	Organization string `json:"organization,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// +optional
 | 	// +optional
 | ||||||
| 	// +kubebuilder:validation:Pattern=`^[^/]*$`
 | 	// +kubebuilder:validation:Pattern=`^[^/]+/[^/]+$`
 | ||||||
| 	Repository string `json:"repository,omitempty"` | 	Repository string `json:"repository,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// +optional
 | 	// +optional
 | ||||||
|  | @ -85,7 +85,7 @@ type RunnerStatus struct { | ||||||
| 
 | 
 | ||||||
| // RunnerStatusRegistration contains runner registration status
 | // RunnerStatusRegistration contains runner registration status
 | ||||||
| type RunnerStatusRegistration struct { | type RunnerStatusRegistration struct { | ||||||
| 	Organization string      `json:"organization"` | 	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"` | ||||||
| 	Token        string      `json:"token"` | 	Token        string      `json:"token"` | ||||||
|  |  | ||||||
|  | @ -4120,11 +4120,10 @@ spec: | ||||||
|                         type: string |                         type: string | ||||||
|                       type: object |                       type: object | ||||||
|                     organization: |                     organization: | ||||||
|                       minLength: 3 |  | ||||||
|                       pattern: ^[^/]+$ |                       pattern: ^[^/]+$ | ||||||
|                       type: string |                       type: string | ||||||
|                     repository: |                     repository: | ||||||
|                       pattern: ^[^/]*$ |                       pattern: ^[^/]+/[^/]+$ | ||||||
|                       type: string |                       type: string | ||||||
|                     resources: |                     resources: | ||||||
|                       description: ResourceRequirements describes the compute resource |                       description: ResourceRequirements describes the compute resource | ||||||
|  | @ -6715,8 +6714,6 @@ spec: | ||||||
|                         - name |                         - name | ||||||
|                         type: object |                         type: object | ||||||
|                       type: array |                       type: array | ||||||
|                   required: |  | ||||||
|                   - organization |  | ||||||
|                   type: object |                   type: object | ||||||
|               type: object |               type: object | ||||||
|           required: |           required: | ||||||
|  |  | ||||||
|  | @ -4120,11 +4120,10 @@ spec: | ||||||
|                         type: string |                         type: string | ||||||
|                       type: object |                       type: object | ||||||
|                     organization: |                     organization: | ||||||
|                       minLength: 3 |  | ||||||
|                       pattern: ^[^/]+$ |                       pattern: ^[^/]+$ | ||||||
|                       type: string |                       type: string | ||||||
|                     repository: |                     repository: | ||||||
|                       pattern: ^[^/]*$ |                       pattern: ^[^/]+/[^/]+$ | ||||||
|                       type: string |                       type: string | ||||||
|                     resources: |                     resources: | ||||||
|                       description: ResourceRequirements describes the compute resource |                       description: ResourceRequirements describes the compute resource | ||||||
|  | @ -6715,8 +6714,6 @@ spec: | ||||||
|                         - name |                         - name | ||||||
|                         type: object |                         type: object | ||||||
|                       type: array |                       type: array | ||||||
|                   required: |  | ||||||
|                   - organization |  | ||||||
|                   type: object |                   type: object | ||||||
|               type: object |               type: object | ||||||
|           required: |           required: | ||||||
|  |  | ||||||
|  | @ -3869,11 +3869,10 @@ spec: | ||||||
|                 type: string |                 type: string | ||||||
|               type: object |               type: object | ||||||
|             organization: |             organization: | ||||||
|               minLength: 3 |  | ||||||
|               pattern: ^[^/]+$ |               pattern: ^[^/]+$ | ||||||
|               type: string |               type: string | ||||||
|             repository: |             repository: | ||||||
|               pattern: ^[^/]*$ |               pattern: ^[^/]+/[^/]+$ | ||||||
|               type: string |               type: string | ||||||
|             resources: |             resources: | ||||||
|               description: ResourceRequirements describes the compute resource requirements. |               description: ResourceRequirements describes the compute resource requirements. | ||||||
|  | @ -6305,8 +6304,6 @@ spec: | ||||||
|                 - name |                 - name | ||||||
|                 type: object |                 type: object | ||||||
|               type: array |               type: array | ||||||
|           required: |  | ||||||
|           - organization |  | ||||||
|           type: object |           type: object | ||||||
|         status: |         status: | ||||||
|           description: RunnerStatus defines the observed state of Runner |           description: RunnerStatus defines the observed state of Runner | ||||||
|  | @ -6335,7 +6332,6 @@ spec: | ||||||
|                   type: string |                   type: string | ||||||
|               required: |               required: | ||||||
|               - expiresAt |               - expiresAt | ||||||
|               - organization |  | ||||||
|               - token |               - token | ||||||
|               type: object |               type: object | ||||||
|           required: |           required: | ||||||
|  |  | ||||||
|  | @ -66,6 +66,12 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { | ||||||
| 		return ctrl.Result{}, client.IgnoreNotFound(err) | 		return ctrl.Result{}, client.IgnoreNotFound(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	err := validateRunnerSpec(&runner.Spec) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Info("Failed to validate runner spec", "error", err.Error()) | ||||||
|  | 		return ctrl.Result{}, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if runner.ObjectMeta.DeletionTimestamp.IsZero() { | 	if runner.ObjectMeta.DeletionTimestamp.IsZero() { | ||||||
| 		finalizers, added := addFinalizer(runner.ObjectMeta.Finalizers) | 		finalizers, added := addFinalizer(runner.ObjectMeta.Finalizers) | ||||||
| 
 | 
 | ||||||
|  | @ -439,3 +445,15 @@ func removeFinalizer(finalizers []string) ([]string, bool) { | ||||||
| 
 | 
 | ||||||
| 	return result, removed | 	return result, removed | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // organization & repository are both exclusive - however this cannot be checked with kubebuilder
 | ||||||
|  | // therefore have an additional check here to log an error in case spec is invalid
 | ||||||
|  | func validateRunnerSpec(spec *v1alpha1.RunnerSpec) error { | ||||||
|  | 	if len(spec.Organization) == 0 && len(spec.Repository) == 0 { | ||||||
|  | 		return fmt.Errorf("RunnerSpec needs organization or repository") | ||||||
|  | 	} | ||||||
|  | 	if len(spec.Organization) > 0 && len(spec.Repository) > 0 { | ||||||
|  | 		return fmt.Errorf("RunnerSpec cannot have both organization and repository") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										108
									
								
								github/github.go
								
								
								
								
							
							
						
						
									
										108
									
								
								github/github.go
								
								
								
								
							|  | @ -4,6 +4,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -47,31 +48,25 @@ func NewClientWithAccessToken(token string) (*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, owner, repo, name string) (*github.RegistrationToken, error) { | func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name string) (*github.RegistrationToken, error) { | ||||||
| 	c.mu.Lock() | 	c.mu.Lock() | ||||||
| 	defer c.mu.Unlock() | 	defer c.mu.Unlock() | ||||||
| 
 | 
 | ||||||
| 	key := owner | 	key := getRegistrationKey(org, repo) | ||||||
| 	if len(repo) > 0 { |  | ||||||
| 		key = fmt.Sprintf("%s/%s", repo, name) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var rt *github.RegistrationToken |  | ||||||
| 
 |  | ||||||
| 	rt, ok := c.regTokens[key] | 	rt, ok := c.regTokens[key] | ||||||
|  | 
 | ||||||
| 	if ok && rt.GetExpiresAt().After(time.Now().Add(-10*time.Minute)) { | 	if ok && rt.GetExpiresAt().After(time.Now().Add(-10*time.Minute)) { | ||||||
| 		return rt, nil | 		return rt, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var res *github.Response | 	owner, repo, err := getOwnerAndRepo(org, repo) | ||||||
| 	var err error |  | ||||||
| 
 | 
 | ||||||
| 	if len(repo) > 0 { | 	if err != nil { | ||||||
| 		rt, res, err = c.Client.Actions.CreateRegistrationToken(ctx, owner, repo) | 		return rt, err | ||||||
| 	} else { |  | ||||||
| 		rt, res, err = CreateOrganizationRegistrationToken(ctx, c, owner) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	rt, res, err := c.createRegistrationToken(ctx, 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) | ||||||
| 	} | 	} | ||||||
|  | @ -89,16 +84,15 @@ func (c *Client) GetRegistrationToken(ctx context.Context, owner, repo, name str | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RemoveRunner removes a runner with specified runner ID from repocitory.
 | // RemoveRunner removes a runner with specified runner ID from repocitory.
 | ||||||
| func (c *Client) RemoveRunner(ctx context.Context, owner, repo string, runnerID int64) error { | func (c *Client) RemoveRunner(ctx context.Context, org, repo string, runnerID int64) error { | ||||||
| 	var res *github.Response | 	owner, repo, err := getOwnerAndRepo(org, repo) | ||||||
| 	var err error |  | ||||||
| 
 | 
 | ||||||
| 	if len(repo) > 0 { | 	if err != nil { | ||||||
| 		res, err = c.Client.Actions.RemoveRunner(ctx, owner, repo, runnerID) | 		return err | ||||||
| 	} else { |  | ||||||
| 		res, err = RemoveOrganizationRunner(ctx, c, owner, runnerID) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	res, err := c.removeRunner(ctx, 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) | ||||||
| 	} | 	} | ||||||
|  | @ -111,20 +105,18 @@ func (c *Client) RemoveRunner(ctx context.Context, owner, repo string, runnerID | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 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, owner, repo string) ([]*github.Runner, error) { | func (c *Client) ListRunners(ctx context.Context, org, repo string) ([]*github.Runner, error) { | ||||||
|  | 	owner, repo, err := getOwnerAndRepo(org, repo) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	var runners []*github.Runner | 	var runners []*github.Runner | ||||||
| 
 | 
 | ||||||
| 	opts := github.ListOptions{PerPage: 10} | 	opts := github.ListOptions{PerPage: 10} | ||||||
| 	for { | 	for { | ||||||
| 		list := &github.Runners{} | 		list, res, err := c.listRunners(ctx, owner, repo, &opts) | ||||||
| 		var res *github.Response |  | ||||||
| 		var err error |  | ||||||
| 
 |  | ||||||
| 		if len(repo) > 0 { |  | ||||||
| 			list, res, err = c.Client.Actions.ListRunners(ctx, owner, repo, &opts) |  | ||||||
| 		} else { |  | ||||||
| 			list, res, err = ListOrganizationRunners(ctx, c, owner, &opts) |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return runners, fmt.Errorf("failed to remove runner: %v", err) | 			return runners, fmt.Errorf("failed to remove runner: %v", err) | ||||||
|  | @ -151,3 +143,57 @@ func (c *Client) cleanup() { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // wrappers for github functions (switch between organization/repository mode)
 | ||||||
|  | // 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) { | ||||||
|  | 	if len(repo) > 0 { | ||||||
|  | 		return c.Client.Actions.CreateRegistrationToken(ctx, owner, repo) | ||||||
|  | 	} else { | ||||||
|  | 		return CreateOrganizationRegistrationToken(ctx, c, 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) | ||||||
|  | 	} else { | ||||||
|  | 		return RemoveOrganizationRunner(ctx, c, 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) | ||||||
|  | 	} else { | ||||||
|  | 		return ListOrganizationRunners(ctx, c, 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 { | ||||||
|  | 		return org, "", nil | ||||||
|  | 	} | ||||||
|  | 	return "", "", fmt.Errorf("organization and repository are both empty") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getRegistrationKey(org, repo string) string { | ||||||
|  | 	if len(org) > 0 { | ||||||
|  | 		return org | ||||||
|  | 	} else { | ||||||
|  | 		return repo | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func splitOwnerAndRepo(repo string) (string, string, error) { | ||||||
|  | 	chunk := strings.Split(repo, "/") | ||||||
|  | 	if len(chunk) != 2 { | ||||||
|  | 		return "", "", fmt.Errorf("invalid repository name: '%s'", repo) | ||||||
|  | 	} | ||||||
|  | 	return chunk[0], chunk[1], nil | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue