diff --git a/admin/server/github.go b/admin/server/github.go index 2a197f6eb30..15b6ddd5283 100644 --- a/admin/server/github.go +++ b/admin/server/github.go @@ -90,12 +90,12 @@ func (s *Server) GetGithubUserStatus(ctx context.Context, req *adminv1.GetGithub client := github.NewTokenClient(ctx, token) // List all the private organizations for the authenticated user - orgs, _, err := client.Organizations.List(ctx, "", nil) + orgs, err := listOrganizations(ctx, client, "") if err != nil { return nil, fmt.Errorf("failed to get user organizations: %w", err) } // List all the public organizations for the authenticated user - publicOrgs, _, err := client.Organizations.List(ctx, user.GithubUsername, nil) + publicOrgs, err := listOrganizations(ctx, client, user.GithubUsername) if err != nil { return nil, fmt.Errorf("failed to get user organizations: %w", err) } @@ -1248,6 +1248,26 @@ func (s *Server) githubAppInstallationURL(state githubConnectState) (string, err return urlutil.MustWithQuery(res, map[string]string{"state": string(stateJSON)}), nil } +// listOrganizations returns all organizations visible for the given user, +// following pagination. An empty user lists the organizations of the +// authenticated user. +func listOrganizations(ctx context.Context, client *github.Client, user string) ([]*github.Organization, error) { + orgs := make([]*github.Organization, 0) + opts := &github.ListOptions{PerPage: 100} + for { + page, resp, err := client.Organizations.List(ctx, user, opts) + if err != nil { + return nil, err + } + orgs = append(orgs, page...) + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + return orgs, nil +} + func fromStringPtr(s *string) string { if s == nil { return "" diff --git a/admin/server/github_test.go b/admin/server/github_test.go index 74e648b34d3..f92ac5d14f3 100644 --- a/admin/server/github_test.go +++ b/admin/server/github_test.go @@ -2,14 +2,47 @@ package server import ( "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" "os" "os/exec" "path/filepath" "testing" + "github.com/google/go-github/v71/github" "github.com/stretchr/testify/require" ) +func TestListOrganizationsPagination(t *testing.T) { + var srv *httptest.Server + srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if r.URL.Query().Get("page") == "2" { + _, _ = w.Write([]byte(`[{"login":"org3"}]`)) + return + } + w.Header().Set("Link", fmt.Sprintf(`<%s%s?page=2>; rel="next"`, srv.URL, r.URL.Path)) + _, _ = w.Write([]byte(`[{"login":"org1"},{"login":"org2"}]`)) + })) + defer srv.Close() + + client := github.NewClient(nil) + base, err := url.Parse(srv.URL + "/") + require.NoError(t, err) + client.BaseURL = base + client.UploadURL = base + + // Organizations of the authenticated user. + orgs, err := listOrganizations(context.Background(), client, "") + require.NoError(t, err) + require.Len(t, orgs, 3) + require.Equal(t, "org1", orgs[0].GetLogin()) + require.Equal(t, "org2", orgs[1].GetLogin()) + require.Equal(t, "org3", orgs[2].GetLogin()) +} + func TestMirrorGitRepo(t *testing.T) { src := setupMirrorSourceRepo(t) dest := t.TempDir()