create.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. })
  54. if err != nil {
  55. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  56. return
  57. }
  58. // write Github actions files to the repo
  59. client, err := getGithubClientFromEnvironment(c.Config(), env)
  60. if err != nil {
  61. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  62. return
  63. }
  64. hooks, _, err := client.Repositories.ListHooks(
  65. r.Context(), owner, name, &github.ListOptions{},
  66. )
  67. if err != nil {
  68. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  69. return
  70. }
  71. webhookURL := fmt.Sprintf("%s/api/github/incoming_webhook", c.Config().ServerConf.ServerURL)
  72. for _, hook := range hooks {
  73. if hook.GetURL() == webhookURL {
  74. // if a previous webhook exists then we should delete it
  75. // this ensures that an updated webhook secret is maintained
  76. _, err = client.Repositories.DeleteHook(
  77. r.Context(), owner, name, hook.GetID(),
  78. )
  79. if err != nil {
  80. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  81. return
  82. }
  83. break
  84. }
  85. }
  86. // create incoming webhook
  87. _, _, err = client.Repositories.CreateHook(
  88. r.Context(), owner, name, &github.Hook{
  89. Config: map[string]interface{}{
  90. "url": webhookURL,
  91. "content_type": "json",
  92. "secret": c.Config().ServerConf.GithubIncomingWebhookSecret,
  93. },
  94. Events: []string{"pull_request"},
  95. Active: github.Bool(false),
  96. },
  97. )
  98. if err != nil {
  99. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  100. return
  101. }
  102. // generate porter jwt token
  103. jwt, err := token.GetTokenForAPI(user.ID, project.ID)
  104. if err != nil {
  105. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  106. return
  107. }
  108. encoded, err := jwt.EncodeToken(c.Config().TokenConf)
  109. if err != nil {
  110. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  111. return
  112. }
  113. err = actions.SetupEnv(&actions.EnvOpts{
  114. Client: client,
  115. ServerURL: c.Config().ServerConf.ServerURL,
  116. PorterToken: encoded,
  117. GitRepoOwner: owner,
  118. GitRepoName: name,
  119. ProjectID: project.ID,
  120. ClusterID: cluster.ID,
  121. GitInstallationID: uint(ga.InstallationID),
  122. EnvironmentName: request.Name,
  123. })
  124. if err != nil {
  125. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  126. return
  127. }
  128. c.WriteResult(w, r, env.ToEnvironmentType())
  129. }
  130. func getGithubClientFromEnvironment(config *config.Config, env *models.Environment) (*github.Client, error) {
  131. // get the github app client
  132. ghAppId, err := strconv.Atoi(config.ServerConf.GithubAppID)
  133. if err != nil {
  134. return nil, err
  135. }
  136. // authenticate as github app installation
  137. itr, err := ghinstallation.NewKeyFromFile(
  138. http.DefaultTransport,
  139. int64(ghAppId),
  140. int64(env.GitInstallationID),
  141. config.ServerConf.GithubAppSecretPath,
  142. )
  143. if err != nil {
  144. return nil, err
  145. }
  146. return github.NewClient(&http.Client{Transport: itr}), nil
  147. }