git_repo_handler.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package api
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "golang.org/x/oauth2"
  10. "github.com/go-chi/chi"
  11. "github.com/google/go-github/github"
  12. "github.com/porter-dev/porter/internal/models"
  13. )
  14. // HandleListProjectGitRepos returns a list of git repos for a project
  15. func (app *App) HandleListProjectGitRepos(w http.ResponseWriter, r *http.Request) {
  16. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  17. if err != nil || projID == 0 {
  18. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  19. return
  20. }
  21. grs, err := app.Repo.GitRepo.ListGitReposByProjectID(uint(projID))
  22. if err != nil {
  23. app.handleErrorRead(err, ErrProjectDataRead, w)
  24. return
  25. }
  26. extGRs := make([]*models.GitRepoExternal, 0)
  27. for _, gr := range grs {
  28. extGRs = append(extGRs, gr.Externalize())
  29. }
  30. w.WriteHeader(http.StatusOK)
  31. if err := json.NewEncoder(w).Encode(extGRs); err != nil {
  32. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  33. return
  34. }
  35. }
  36. // Repo represents a GitHub or Gitab repository
  37. type Repo struct {
  38. FullName string
  39. Kind string
  40. }
  41. // DirectoryItem represents a file or subfolder in a repository
  42. type DirectoryItem struct {
  43. Path string
  44. Type string
  45. }
  46. // HandleListRepos retrieves a list of repo names
  47. func (app *App) HandleListRepos(w http.ResponseWriter, r *http.Request) {
  48. tok, err := app.githubTokenFromRequest(r)
  49. if err != nil {
  50. app.handleErrorInternal(err, w)
  51. return
  52. }
  53. res := make([]Repo, 0)
  54. client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
  55. allRepos := make([]*github.Repository, 0)
  56. opt := &github.RepositoryListOptions{
  57. ListOptions: github.ListOptions{
  58. PerPage: 100,
  59. },
  60. Sort: "updated",
  61. }
  62. for {
  63. repos, resp, err := client.Repositories.List(context.Background(), "", opt)
  64. if err != nil {
  65. app.handleErrorInternal(err, w)
  66. return
  67. }
  68. allRepos = append(allRepos, repos...)
  69. if resp.NextPage == 0 {
  70. break
  71. }
  72. opt.Page = resp.NextPage
  73. }
  74. for _, repo := range allRepos {
  75. res = append(res, Repo{
  76. FullName: repo.GetFullName(),
  77. Kind: "github",
  78. })
  79. }
  80. json.NewEncoder(w).Encode(res)
  81. }
  82. // HandleDeleteProjectGitRepo handles the deletion of a Github Repo via the git repo ID
  83. func (app *App) HandleDeleteProjectGitRepo(w http.ResponseWriter, r *http.Request) {
  84. id, err := strconv.ParseUint(chi.URLParam(r, "git_repo_id"), 0, 64)
  85. if err != nil || id == 0 {
  86. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  87. return
  88. }
  89. repo, err := app.Repo.GitRepo.ReadGitRepo(uint(id))
  90. if err != nil {
  91. app.handleErrorRead(err, ErrProjectDataRead, w)
  92. return
  93. }
  94. err = app.Repo.GitRepo.DeleteGitRepo(repo)
  95. if err != nil {
  96. app.handleErrorRead(err, ErrProjectDataRead, w)
  97. return
  98. }
  99. w.WriteHeader(http.StatusOK)
  100. }
  101. // HandleGetBranches retrieves a list of branch names for a specified repo
  102. func (app *App) HandleGetBranches(w http.ResponseWriter, r *http.Request) {
  103. tok, err := app.githubTokenFromRequest(r)
  104. if err != nil {
  105. app.handleErrorInternal(err, w)
  106. return
  107. }
  108. owner := chi.URLParam(r, "owner")
  109. name := chi.URLParam(r, "name")
  110. client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
  111. // List all branches for a specified repo
  112. branches, _, err := client.Repositories.ListBranches(context.Background(), owner, name, nil)
  113. if err != nil {
  114. return
  115. }
  116. res := []string{}
  117. for _, b := range branches {
  118. res = append(res, b.GetName())
  119. }
  120. json.NewEncoder(w).Encode(res)
  121. }
  122. // HandleGetBranchContents retrieves the contents of a specific branch and subdirectory
  123. func (app *App) HandleGetBranchContents(w http.ResponseWriter, r *http.Request) {
  124. tok, err := app.githubTokenFromRequest(r)
  125. if err != nil {
  126. app.handleErrorInternal(err, w)
  127. return
  128. }
  129. client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
  130. queryParams, err := url.ParseQuery(r.URL.RawQuery)
  131. if err != nil {
  132. app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
  133. return
  134. }
  135. owner := chi.URLParam(r, "owner")
  136. name := chi.URLParam(r, "name")
  137. branch := chi.URLParam(r, "branch")
  138. repoContentOptions := github.RepositoryContentGetOptions{}
  139. repoContentOptions.Ref = branch
  140. _, directoryContents, _, err := client.Repositories.GetContents(context.Background(), owner, name, queryParams["dir"][0], &repoContentOptions)
  141. if err != nil {
  142. app.handleErrorInternal(err, w)
  143. return
  144. }
  145. res := []DirectoryItem{}
  146. for i := range directoryContents {
  147. d := DirectoryItem{}
  148. d.Path = *directoryContents[i].Path
  149. d.Type = *directoryContents[i].Type
  150. res = append(res, d)
  151. }
  152. // Ret2: recursively traverse all dirs to create config bundle (case on type == dir)
  153. // https://api.github.com/repos/porter-dev/porter/contents?ref=frontend-graph
  154. json.NewEncoder(w).Encode(res)
  155. }
  156. // finds the github token given the git repo id and the project id
  157. func (app *App) githubTokenFromRequest(
  158. r *http.Request,
  159. ) (*oauth2.Token, error) {
  160. grID, err := strconv.ParseUint(chi.URLParam(r, "git_repo_id"), 0, 64)
  161. if err != nil || grID == 0 {
  162. return nil, fmt.Errorf("could not read git repo id")
  163. }
  164. // query for the git repo
  165. gr, err := app.Repo.GitRepo.ReadGitRepo(uint(grID))
  166. if err != nil {
  167. return nil, err
  168. }
  169. // get the oauth integration
  170. oauthInt, err := app.Repo.OAuthIntegration.ReadOAuthIntegration(gr.OAuthIntegrationID)
  171. if err != nil {
  172. return nil, err
  173. }
  174. return &oauth2.Token{
  175. AccessToken: string(oauthInt.AccessToken),
  176. RefreshToken: string(oauthInt.RefreshToken),
  177. TokenType: "Bearer",
  178. }, nil
  179. }