Find runner groups that visible to repository using a single API call. (#1324)
The [ListRunnerGroup API](https://docs.github.com/en/rest/reference/actions#list-self-hosted-runner-groups-for-an-organization) now add a new query parameter `visible_to_repository`. We were doing `N+1` lookup when trying to find which runner group can be used for job from a certain repository. - List all runner groups - Loop through all groups to check repository access for each of them via [API](https://docs.github.com/en/rest/reference/actions#list-repository-access-to-a-self-hosted-runner-group-in-an-organization) The new query parameter `visible_to_repository` should allow us to get the runner groups with access in one call. Limitation: - The new query parameter is only supported in GitHub.com, which means anyone who uses ARC in GitHub Enterprise Server won't get this. - I am working on a PR to update `go-github` library to support the new parameter, but it will take a few weeks for a newer `go-github` to be released, so in the meantime, I am duplicating the implementation in ARC as well to support the new query parameter.
This commit is contained in:
		
							parent
							
								
									c3e280eadb
								
							
						
					
					
						commit
						ebe7d060cb
					
				|  | @ -265,6 +265,29 @@ func (c *Client) ListOrganizationRunnerGroups(ctx context.Context, org string) ( | ||||||
| 	return runnerGroups, nil | 	return runnerGroups, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ListOrganizationRunnerGroupsForRepository returns all the runner groups defined in the organization and
 | ||||||
|  | // inherited to the organization from an enterprise.
 | ||||||
|  | // We can remove this when google/go-github library is updated to support this.
 | ||||||
|  | func (c *Client) ListOrganizationRunnerGroupsForRepository(ctx context.Context, org, repo string) ([]*github.RunnerGroup, error) { | ||||||
|  | 	var runnerGroups []*github.RunnerGroup | ||||||
|  | 
 | ||||||
|  | 	opts := github.ListOptions{PerPage: 100} | ||||||
|  | 	for { | ||||||
|  | 		list, res, err := c.listOrganizationRunnerGroupsVisibleToRepo(ctx, org, repo, &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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *Client) ListRunnerGroupRepositoryAccesses(ctx context.Context, org string, runnerGroupId int64) ([]*github.Repository, error) { | func (c *Client) ListRunnerGroupRepositoryAccesses(ctx context.Context, org string, runnerGroupId int64) ([]*github.Repository, error) { | ||||||
| 	var repos []*github.Repository | 	var repos []*github.Repository | ||||||
| 
 | 
 | ||||||
|  | @ -286,6 +309,42 @@ func (c *Client) ListRunnerGroupRepositoryAccesses(ctx context.Context, org stri | ||||||
| 	return repos, nil | 	return repos, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // listOrganizationRunnerGroupsVisibleToRepo lists all self-hosted runner groups configured in an organization which can be used by the repository.
 | ||||||
|  | //
 | ||||||
|  | // GitHub API docs: https://docs.github.com/en/rest/reference/actions#list-self-hosted-runner-groups-for-an-organization
 | ||||||
|  | func (c *Client) listOrganizationRunnerGroupsVisibleToRepo(ctx context.Context, org, repo string, opts *github.ListOptions) (*github.RunnerGroups, *github.Response, error) { | ||||||
|  | 	repoName := repo | ||||||
|  | 	parts := strings.Split(repo, "/") | ||||||
|  | 	if len(parts) == 2 { | ||||||
|  | 		repoName = parts[1] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	u := fmt.Sprintf("orgs/%v/actions/runner-groups?visible_to_repository=%v", org, repoName) | ||||||
|  | 
 | ||||||
|  | 	if opts != nil { | ||||||
|  | 		if opts.PerPage > 0 { | ||||||
|  | 			u = fmt.Sprintf("%v&per_page=%v", u, opts.PerPage) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if opts.Page > 0 { | ||||||
|  | 			u = fmt.Sprintf("%v&page=%v", u, opts.Page) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	req, err := c.Client.NewRequest("GET", u, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	groups := &github.RunnerGroups{} | ||||||
|  | 	resp, err := c.Client.Do(ctx, req, &groups) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, resp, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return groups, resp, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // cleanup removes expired registration tokens.
 | // cleanup removes expired registration tokens.
 | ||||||
| func (c *Client) cleanup() { | func (c *Client) cleanup() { | ||||||
| 	c.mu.Lock() | 	c.mu.Lock() | ||||||
|  |  | ||||||
|  | @ -18,30 +18,47 @@ func (c *Simulator) GetRunnerGroupsVisibleToRepository(ctx context.Context, org, | ||||||
| 		panic(fmt.Sprintf("BUG: owner should not be empty in this context. repo=%v", repo)) | 		panic(fmt.Sprintf("BUG: owner should not be empty in this context. repo=%v", repo)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	runnerGroups, err := c.Client.ListOrganizationRunnerGroups(ctx, org) | 	if c.Client.GithubBaseURL == "https://github.com/" { | ||||||
| 	if err != nil { | 		runnerGroups, err := c.Client.ListOrganizationRunnerGroupsForRepository(ctx, org, repo) | ||||||
| 		return visible, err | 		if err != nil { | ||||||
| 	} | 			return visible, err | ||||||
| 
 |  | ||||||
| 	for _, runnerGroup := range runnerGroups { |  | ||||||
| 		ref := NewRunnerGroupFromGitHub(runnerGroup) |  | ||||||
| 
 |  | ||||||
| 		if !managed.Includes(ref) { |  | ||||||
| 			continue |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if runnerGroup.GetVisibility() != "all" { | 		for _, runnerGroup := range runnerGroups { | ||||||
| 			hasAccess, err := c.hasRepoAccessToOrganizationRunnerGroup(ctx, org, runnerGroup.GetID(), repo) | 			ref := NewRunnerGroupFromGitHub(runnerGroup) | ||||||
| 			if err != nil { |  | ||||||
| 				return visible, err |  | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			if !hasAccess { | 			if !managed.Includes(ref) { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
|  | 			visible.Add(ref) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		runnerGroups, err := c.Client.ListOrganizationRunnerGroups(ctx, org) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return visible, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		visible.Add(ref) | 		for _, runnerGroup := range runnerGroups { | ||||||
|  | 			ref := NewRunnerGroupFromGitHub(runnerGroup) | ||||||
|  | 
 | ||||||
|  | 			if !managed.Includes(ref) { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if runnerGroup.GetVisibility() != "all" { | ||||||
|  | 				hasAccess, err := c.hasRepoAccessToOrganizationRunnerGroup(ctx, org, runnerGroup.GetID(), repo) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return visible, err | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if !hasAccess { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			visible.Add(ref) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return visible, nil | 	return visible, nil | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue