create.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package environment
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. ghinstallation "github.com/bradleyfalzon/ghinstallation/v2"
  8. "github.com/google/go-github/v41/github"
  9. "github.com/porter-dev/porter/api/server/handlers"
  10. "github.com/porter-dev/porter/api/server/handlers/gitinstallation"
  11. "github.com/porter-dev/porter/api/server/shared"
  12. "github.com/porter-dev/porter/api/server/shared/apierrors"
  13. "github.com/porter-dev/porter/api/server/shared/config"
  14. "github.com/porter-dev/porter/api/types"
  15. "github.com/porter-dev/porter/internal/auth/token"
  16. "github.com/porter-dev/porter/internal/encryption"
  17. "github.com/porter-dev/porter/internal/integrations/ci/actions"
  18. "github.com/porter-dev/porter/internal/models"
  19. "github.com/porter-dev/porter/internal/models/integrations"
  20. )
  21. type CreateEnvironmentHandler struct {
  22. handlers.PorterHandlerReadWriter
  23. }
  24. func NewCreateEnvironmentHandler(
  25. config *config.Config,
  26. decoderValidator shared.RequestDecoderValidator,
  27. writer shared.ResultWriter,
  28. ) *CreateEnvironmentHandler {
  29. return &CreateEnvironmentHandler{
  30. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  31. }
  32. }
  33. func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. ga, _ := r.Context().Value(types.GitInstallationScope).(*integrations.GithubAppInstallation)
  35. user, _ := r.Context().Value(types.UserScope).(*models.User)
  36. project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  37. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  38. owner, name, ok := gitinstallation.GetOwnerAndNameParams(c, w, r)
  39. if !ok {
  40. return
  41. }
  42. // create the environment
  43. request := &types.CreateEnvironmentRequest{}
  44. if ok := c.DecodeAndValidate(w, r, request); !ok {
  45. return
  46. }
  47. // create a random webhook id
  48. webhookUID, err := encryption.GenerateRandomBytes(32)
  49. if err != nil {
  50. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  51. return
  52. }
  53. env, err := c.Repo().Environment().CreateEnvironment(&models.Environment{
  54. ProjectID: project.ID,
  55. ClusterID: cluster.ID,
  56. GitInstallationID: uint(ga.InstallationID),
  57. Name: request.Name,
  58. GitRepoOwner: owner,
  59. GitRepoName: name,
  60. Mode: request.Mode,
  61. WebhookID: string(webhookUID),
  62. })
  63. if err != nil {
  64. c.deleteEnvAndReportError(w, r, env, err)
  65. return
  66. }
  67. // write Github actions files to the repo
  68. client, err := getGithubClientFromEnvironment(c.Config(), env)
  69. if err != nil {
  70. c.deleteEnvAndReportError(w, r, env, err)
  71. return
  72. }
  73. webhookURL := fmt.Sprintf("%s/api/github/incoming_webhook/%s", c.Config().ServerConf.ServerURL, string(webhookUID))
  74. // create incoming webhook
  75. _, _, err = client.Repositories.CreateHook(
  76. r.Context(), owner, name, &github.Hook{
  77. Config: map[string]interface{}{
  78. "url": webhookURL,
  79. "content_type": "json",
  80. "secret": c.Config().ServerConf.GithubIncomingWebhookSecret,
  81. },
  82. Events: []string{"pull_request"},
  83. Active: github.Bool(true),
  84. },
  85. )
  86. if err != nil && !strings.Contains(err.Error(), "already exists on this repository") {
  87. c.deleteEnvAndReportError(w, r, env, err)
  88. return
  89. }
  90. // generate porter jwt token
  91. jwt, err := token.GetTokenForAPI(user.ID, project.ID)
  92. if err != nil {
  93. c.deleteEnvAndReportError(w, r, env, err)
  94. return
  95. }
  96. encoded, err := jwt.EncodeToken(c.Config().TokenConf)
  97. if err != nil {
  98. c.deleteEnvAndReportError(w, r, env, err)
  99. return
  100. }
  101. err = actions.SetupEnv(&actions.EnvOpts{
  102. Client: client,
  103. ServerURL: c.Config().ServerConf.ServerURL,
  104. PorterToken: encoded,
  105. GitRepoOwner: owner,
  106. GitRepoName: name,
  107. ProjectID: project.ID,
  108. ClusterID: cluster.ID,
  109. GitInstallationID: uint(ga.InstallationID),
  110. EnvironmentName: request.Name,
  111. })
  112. if err != nil {
  113. c.deleteEnvAndReportError(w, r, env, err)
  114. return
  115. }
  116. c.WriteResult(w, r, env.ToEnvironmentType())
  117. }
  118. func (c *CreateEnvironmentHandler) deleteEnvAndReportError(
  119. w http.ResponseWriter, r *http.Request, env *models.Environment, err error,
  120. ) {
  121. c.Repo().Environment().DeleteEnvironment(env)
  122. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  123. }
  124. func getGithubClientFromEnvironment(config *config.Config, env *models.Environment) (*github.Client, error) {
  125. // get the github app client
  126. ghAppId, err := strconv.Atoi(config.ServerConf.GithubAppID)
  127. if err != nil {
  128. return nil, err
  129. }
  130. // authenticate as github app installation
  131. itr, err := ghinstallation.NewKeyFromFile(
  132. http.DefaultTransport,
  133. int64(ghAppId),
  134. int64(env.GitInstallationID),
  135. config.ServerConf.GithubAppSecretPath,
  136. )
  137. if err != nil {
  138. return nil, err
  139. }
  140. return github.NewClient(&http.Client{Transport: itr}), nil
  141. }