git_action_handler.go 6.5 KB

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