git_action_handler.go 6.6 KB

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