git_action_handler.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "github.com/go-chi/chi"
  10. "github.com/porter-dev/porter/internal/auth/token"
  11. "github.com/porter-dev/porter/internal/forms"
  12. "github.com/porter-dev/porter/internal/integrations/ci/actions"
  13. "github.com/porter-dev/porter/internal/models"
  14. "github.com/porter-dev/porter/internal/registry"
  15. )
  16. const (
  17. updateAppActionVersion = "v0.1.0"
  18. )
  19. // HandleGenerateGitAction returns the Github action that will be created in a repository
  20. // for a given release
  21. func (app *App) HandleGenerateGitAction(w http.ResponseWriter, r *http.Request) {
  22. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 10, 64)
  23. if err != nil || projID == 0 {
  24. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  25. return
  26. }
  27. vals, err := url.ParseQuery(r.URL.RawQuery)
  28. name := vals["name"][0]
  29. clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
  30. if err != nil {
  31. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  32. Code: ErrReleaseReadData,
  33. Errors: []string{"release not found"},
  34. }, w)
  35. return
  36. }
  37. form := &forms.CreateGitAction{
  38. ShouldGenerateOnly: true,
  39. }
  40. // decode from JSON to form value
  41. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  42. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  43. return
  44. }
  45. _, workflowYAML := app.createGitActionFromForm(projID, clusterID, name, form, w, r)
  46. w.WriteHeader(http.StatusOK)
  47. if _, err := w.Write(workflowYAML); err != nil {
  48. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  49. return
  50. }
  51. }
  52. // HandleCreateGitAction creates a new Github action in a repository for a given
  53. // release
  54. func (app *App) HandleCreateGitAction(w http.ResponseWriter, r *http.Request) {
  55. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  56. if err != nil || projID == 0 {
  57. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  58. return
  59. }
  60. vals, err := url.ParseQuery(r.URL.RawQuery)
  61. name := vals["name"][0]
  62. namespace := vals["namespace"][0]
  63. clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
  64. if err != nil {
  65. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  66. Code: ErrReleaseReadData,
  67. Errors: []string{"release not found"},
  68. }, w)
  69. }
  70. release, err := app.Repo.Release().ReadRelease(uint(clusterID), name, namespace)
  71. if err != nil {
  72. app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
  73. Code: ErrReleaseReadData,
  74. Errors: []string{"release not found"},
  75. }, w)
  76. }
  77. form := &forms.CreateGitAction{
  78. Release: release,
  79. ShouldGenerateOnly: false,
  80. }
  81. // decode from JSON to form value
  82. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  83. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  84. return
  85. }
  86. gaExt, _ := app.createGitActionFromForm(projID, clusterID, name, form, w, r)
  87. w.WriteHeader(http.StatusCreated)
  88. if err := json.NewEncoder(w).Encode(gaExt); err != nil {
  89. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  90. return
  91. }
  92. }
  93. func (app *App) createGitActionFromForm(
  94. projID,
  95. clusterID uint64,
  96. name string,
  97. form *forms.CreateGitAction,
  98. w http.ResponseWriter,
  99. r *http.Request,
  100. ) (gaExt *models.GitActionConfigExternal, workflowYAML []byte) {
  101. // validate the form
  102. if err := app.validator.Struct(form); err != nil {
  103. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  104. return
  105. }
  106. // if the registry was provisioned through Porter, create a repository if necessary
  107. if form.RegistryID != 0 {
  108. // read the registry
  109. reg, err := app.Repo.Registry().ReadRegistry(form.RegistryID)
  110. if err != nil {
  111. app.handleErrorDataRead(err, w)
  112. return
  113. }
  114. _reg := registry.Registry(*reg)
  115. regAPI := &_reg
  116. // parse the name from the registry
  117. nameSpl := strings.Split(form.ImageRepoURI, "/")
  118. repoName := nameSpl[len(nameSpl)-1]
  119. err = regAPI.CreateRepository(app.Repo, repoName)
  120. if err != nil {
  121. app.handleErrorInternal(err, w)
  122. return
  123. }
  124. }
  125. repoSplit := strings.Split(form.GitRepo, "/")
  126. if len(repoSplit) != 2 {
  127. app.handleErrorFormDecoding(fmt.Errorf("invalid formatting of repo name"), ErrProjectDecode, w)
  128. return
  129. }
  130. session, err := app.Store.Get(r, app.ServerConf.CookieName)
  131. if err != nil {
  132. http.Error(w, err.Error(), http.StatusInternalServerError)
  133. return
  134. }
  135. userID, _ := session.Values["user_id"].(uint)
  136. if userID == 0 {
  137. tok := app.getTokenFromRequest(r)
  138. if tok != nil && tok.IBy != 0 {
  139. userID = tok.IBy
  140. } else if tok == nil || tok.IBy == 0 {
  141. http.Error(w, "no user id found in request", http.StatusInternalServerError)
  142. return
  143. }
  144. }
  145. // generate porter jwt token
  146. jwt, _ := token.GetTokenForAPI(userID, uint(projID))
  147. encoded, err := jwt.EncodeToken(&token.TokenGeneratorConf{
  148. TokenSecret: app.ServerConf.TokenGeneratorSecret,
  149. })
  150. if err != nil {
  151. app.handleErrorInternal(err, w)
  152. return
  153. }
  154. // create the commit in the git repo
  155. gaRunner := &actions.GithubActions{
  156. ServerURL: app.ServerConf.ServerURL,
  157. GithubOAuthIntegration: nil,
  158. GithubAppID: app.GithubAppConf.AppID,
  159. GithubAppSecretPath: app.GithubAppConf.SecretPath,
  160. GithubInstallationID: form.GitRepoID,
  161. GitRepoName: repoSplit[1],
  162. GitRepoOwner: repoSplit[0],
  163. Repo: app.Repo,
  164. GithubConf: app.GithubProjectConf,
  165. ProjectID: uint(projID),
  166. ClusterID: uint(clusterID),
  167. ReleaseName: name,
  168. GitBranch: form.GitBranch,
  169. DockerFilePath: form.DockerfilePath,
  170. FolderPath: form.FolderPath,
  171. ImageRepoURL: form.ImageRepoURI,
  172. PorterToken: encoded,
  173. Version: updateAppActionVersion,
  174. ShouldGenerateOnly: form.ShouldGenerateOnly,
  175. ShouldCreateWorkflow: form.ShouldCreateWorkflow,
  176. }
  177. workflowYAML, err = gaRunner.Setup()
  178. if err != nil {
  179. app.handleErrorInternal(err, w)
  180. return
  181. }
  182. if form.Release == nil {
  183. return
  184. }
  185. // convert the form to a git action config
  186. gitAction, err := form.ToGitActionConfig(gaRunner.Version)
  187. if err != nil {
  188. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  189. return
  190. }
  191. // handle write to the database
  192. ga, err := app.Repo.GitActionConfig().CreateGitActionConfig(gitAction)
  193. if err != nil {
  194. app.handleErrorDataWrite(err, w)
  195. return
  196. }
  197. app.Logger.Info().Msgf("New git action created: %d", ga.ID)
  198. // update the release in the db with the image repo uri
  199. form.Release.ImageRepoURI = gitAction.ImageRepoURI
  200. _, err = app.Repo.Release().UpdateRelease(form.Release)
  201. if err != nil {
  202. app.handleErrorDataWrite(err, w)
  203. return
  204. }
  205. gaExt = ga.Externalize()
  206. return
  207. }