Alexander Belanger преди 4 години
родител
ревизия
55ba6f5346

+ 23 - 0
api/server/handlers/gitinstallation/helpers.go

@@ -3,9 +3,12 @@ package gitinstallation
 import (
 	"net/http"
 
+	"github.com/bradleyfalzon/ghinstallation"
+	"github.com/google/go-github/github"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/models/integrations"
 	"github.com/porter-dev/porter/internal/oauth"
 	"golang.org/x/oauth2"
 )
@@ -47,3 +50,23 @@ func GetGithubAppOauthTokenFromRequest(config *config.Config, r *http.Request) (
 		TokenType:    "Bearer",
 	}, nil
 }
+
+// GetGithubAppClientFromRequest gets the github app installation id from the request and authenticates
+// using it and a private key file
+func GetGithubAppClientFromRequest(config *config.Config, r *http.Request) (*github.Client, error) {
+	// get installation id from context
+	ga, _ := r.Context().Value(types.GitInstallationScope).(*integrations.GithubAppInstallation)
+
+	itr, err := ghinstallation.NewKeyFromFile(
+		http.DefaultTransport,
+		config.GithubAppConf.AppID,
+		int64(ga.ID),
+		config.GithubAppConf.SecretPath,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return github.NewClient(&http.Client{Transport: itr}), nil
+}

+ 1 - 2
api/server/handlers/gitinstallation/list.go

@@ -2,7 +2,6 @@ package gitinstallation
 
 import (
 	"context"
-	"encoding/json"
 	"net/http"
 
 	"github.com/google/go-github/github"
@@ -93,5 +92,5 @@ func (c *GitRepoListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		installationIds = append(installationIds, v.InstallationID)
 	}
 
-	json.NewEncoder(w).Encode(installationIds)
+	c.WriteResult(w, r, installationIds)
 }

+ 115 - 0
api/server/handlers/gitinstallation/list_repos.go

@@ -0,0 +1,115 @@
+package gitinstallation
+
+import (
+	"context"
+	"net/http"
+	"sync"
+
+	"github.com/google/go-github/github"
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+)
+
+type GithubListReposHandler struct {
+	handlers.PorterHandlerWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewGithubListReposHandler(
+	config *config.Config,
+	writer shared.ResultWriter,
+) *GithubListReposHandler {
+	return &GithubListReposHandler{
+		PorterHandlerWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
+	}
+}
+
+func (c *GithubListReposHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	client, err := GetGithubAppClientFromRequest(c.Config(), r)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// figure out number of repositories
+	opt := &github.ListOptions{
+		PerPage: 100,
+	}
+
+	allRepos, resp, err := client.Apps.ListRepos(context.Background(), opt)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// make workers to get pages concurrently
+	const WCOUNT = 5
+	numPages := resp.LastPage + 1
+	var workerErr error
+	var mu sync.Mutex
+	var wg sync.WaitGroup
+
+	worker := func(cp int) {
+		defer wg.Done()
+
+		for cp < numPages {
+			cur_opt := &github.ListOptions{
+				Page:    cp,
+				PerPage: 100,
+			}
+
+			repos, _, err := client.Apps.ListRepos(context.Background(), cur_opt)
+
+			if err != nil {
+				mu.Lock()
+				workerErr = err
+				mu.Unlock()
+				return
+			}
+
+			mu.Lock()
+			allRepos = append(allRepos, repos...)
+			mu.Unlock()
+
+			cp += WCOUNT
+		}
+	}
+
+	var numJobs int
+	if numPages > WCOUNT {
+		numJobs = WCOUNT
+	} else {
+		numJobs = numPages
+	}
+
+	wg.Add(numJobs)
+
+	// page 1 is already loaded so we start with 2
+	for i := 1; i <= numJobs; i++ {
+		go worker(i + 1)
+	}
+
+	wg.Wait()
+
+	if workerErr != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	res := make(types.ListReposResponse, 0)
+
+	for _, repo := range allRepos {
+		res = append(res, types.Repo{
+			FullName: repo.GetFullName(),
+			Kind:     "github",
+		})
+	}
+
+	c.WriteResult(w, r, res)
+}

+ 29 - 0
api/server/router/git_installation.go

@@ -80,5 +80,34 @@ func getGitInstallationRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/gitrepos/{git_installation_id}/repos ->
+	// gitinstallation.GithubListReposHandler
+	listReposEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbList,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/repos",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.GitInstallationScope,
+			},
+		},
+	)
+
+	listReposHandler := gitinstallation.NewGithubListReposHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: listReposEndpoint,
+		Handler:  listReposHandler,
+		Router:   r,
+	})
+
 	return routes, newPath
 }

+ 8 - 0
api/types/git_installation.go

@@ -13,3 +13,11 @@ type GitInstallation struct {
 type GetGitInstallationResponse GitInstallation
 
 type ListGitInstallationIDsResponse []int64
+
+// Repo represents a GitHub or Gitab repository
+type Repo struct {
+	FullName string
+	Kind     string
+}
+
+type ListReposResponse []Repo

+ 1 - 1
docs/developing/backend-refactor-status.md

@@ -51,7 +51,7 @@
 | <li>- [ ] `POST /api/projects/{project_id}/deploy/addon/{name}/{version}`                                                   |             |                 |             |                  |
 | <li>- [ ] `POST /api/projects/{project_id}/deploy/{name}/{version}`                                                         |             |                 |             |                  |
 | <li>- [X] `GET /api/projects/{project_id}/gitrepos`                                                                         | AB          |                 |             |                  |
-| <li>- [ ] `GET /api/projects/{project_id}/gitrepos/{installation_id}/repos`                                                 |             |                 |             |                  |
+| <li>- [X] `GET /api/projects/{project_id}/gitrepos/{installation_id}/repos`                                                 | AB          |                 |             |                  |
 | <li>- [ ] `GET /api/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/branches`                  |             |                 |             |                  |
 | <li>- [ ] `GET /api/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/{branch}/buildpack/detect` |             |                 |             |                  |
 | <li>- [ ] `GET /api/projects/{project_id}/gitrepos/{installation_id}/repos/{kind}/{owner}/{name}/{branch}/contents`         |             |                 |             |                  |