create.go 4.7 KB

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