github_incoming.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package webhook
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strconv"
  6. "github.com/bradleyfalzon/ghinstallation/v2"
  7. "github.com/google/go-github/v41/github"
  8. "github.com/porter-dev/porter/api/server/authz"
  9. "github.com/porter-dev/porter/api/server/handlers"
  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/server/shared/requestutils"
  14. "github.com/porter-dev/porter/api/types"
  15. "github.com/porter-dev/porter/internal/models"
  16. )
  17. type GithubIncomingWebhookHandler struct {
  18. handlers.PorterHandlerReadWriter
  19. authz.KubernetesAgentGetter
  20. }
  21. func NewGithubIncomingWebhookHandler(
  22. config *config.Config,
  23. decoderValidator shared.RequestDecoderValidator,
  24. writer shared.ResultWriter,
  25. ) *GithubIncomingWebhookHandler {
  26. return &GithubIncomingWebhookHandler{
  27. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  28. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  29. }
  30. }
  31. func (c *GithubIncomingWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  32. payload, err := github.ValidatePayload(r, []byte(c.Config().ServerConf.GithubIncomingWebhookSecret))
  33. if err != nil {
  34. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  35. return
  36. }
  37. event, err := github.ParseWebHook(github.WebHookType(r), payload)
  38. if err != nil {
  39. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  40. return
  41. }
  42. switch event := event.(type) {
  43. case *github.PullRequestEvent:
  44. err = c.processPullRequestEvent(event, r)
  45. if err != nil {
  46. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  47. return
  48. }
  49. }
  50. }
  51. func (c *GithubIncomingWebhookHandler) processPullRequestEvent(event *github.PullRequestEvent, r *http.Request) error {
  52. // get the webhook id from the request
  53. webhookID, reqErr := requestutils.GetURLParamString(r, types.URLParamIncomingWebhookID)
  54. if reqErr != nil {
  55. return fmt.Errorf(reqErr.Error())
  56. }
  57. owner := event.GetRepo().GetOwner().GetLogin()
  58. repo := event.GetRepo().GetName()
  59. env, err := c.Repo().Environment().ReadEnvironmentByWebhookIDOwnerRepoName(webhookID, owner, repo)
  60. if err != nil {
  61. return err
  62. }
  63. // create deployment on GitHub API
  64. client, err := getGithubClientFromEnvironment(c.Config(), env)
  65. if err != nil {
  66. return err
  67. }
  68. if env.Mode == "auto" && event.GetAction() == "opened" {
  69. _, err := client.Actions.CreateWorkflowDispatchEventByFileName(
  70. r.Context(), owner, repo, fmt.Sprintf("porter_%s_env.yml", env.Name),
  71. github.CreateWorkflowDispatchEventRequest{
  72. Ref: event.PullRequest.GetHead().GetRef(),
  73. Inputs: map[string]interface{}{
  74. "pr_number": strconv.FormatUint(uint64(event.PullRequest.GetNumber()), 10),
  75. "pr_title": event.PullRequest.GetTitle(),
  76. "pr_branch_from": event.PullRequest.GetHead().GetRef(),
  77. "pr_branch_into": event.PullRequest.GetBase().GetRef(),
  78. },
  79. },
  80. )
  81. if err != nil {
  82. return err
  83. }
  84. } else if event.GetAction() == "synchronize" || event.GetAction() == "closed" {
  85. depl, err := c.Repo().Environment().ReadDeploymentByGitDetails(
  86. env.ID, owner, repo, uint(event.GetPullRequest().GetNumber()),
  87. )
  88. if err != nil {
  89. return err
  90. }
  91. if depl.Status != types.DeploymentStatusInactive {
  92. if event.GetAction() == "synchronize" {
  93. _, err := client.Actions.CreateWorkflowDispatchEventByFileName(
  94. r.Context(), owner, repo, fmt.Sprintf("porter_%s_env.yml", env.Name),
  95. github.CreateWorkflowDispatchEventRequest{
  96. Ref: event.PullRequest.GetHead().GetRef(),
  97. Inputs: map[string]interface{}{
  98. "pr_number": strconv.FormatUint(uint64(event.PullRequest.GetNumber()), 10),
  99. "pr_title": event.PullRequest.GetTitle(),
  100. "pr_branch_from": event.PullRequest.GetHead().GetRef(),
  101. "pr_branch_into": event.PullRequest.GetBase().GetRef(),
  102. },
  103. },
  104. )
  105. if err != nil {
  106. return err
  107. }
  108. } else {
  109. _, err := client.Actions.CreateWorkflowDispatchEventByFileName(
  110. r.Context(), owner, repo, fmt.Sprintf("porter_%s_delete_env.yml", env.Name),
  111. github.CreateWorkflowDispatchEventRequest{
  112. Ref: event.PullRequest.GetHead().GetRef(),
  113. Inputs: map[string]interface{}{
  114. "environment_id": strconv.FormatUint(uint64(depl.EnvironmentID), 10),
  115. "repo_owner": owner,
  116. "repo_name": repo,
  117. "pr_number": strconv.FormatUint(uint64(event.PullRequest.GetNumber()), 10),
  118. },
  119. },
  120. )
  121. if err != nil {
  122. return err
  123. }
  124. }
  125. }
  126. }
  127. return nil
  128. }
  129. func getGithubClientFromEnvironment(config *config.Config, env *models.Environment) (*github.Client, error) {
  130. // get the github app client
  131. ghAppId, err := strconv.Atoi(config.ServerConf.GithubAppID)
  132. if err != nil {
  133. return nil, err
  134. }
  135. // authenticate as github app installation
  136. itr, err := ghinstallation.NewKeyFromFile(
  137. http.DefaultTransport,
  138. int64(ghAppId),
  139. int64(env.GitInstallationID),
  140. config.ServerConf.GithubAppSecretPath,
  141. )
  142. if err != nil {
  143. return nil, err
  144. }
  145. return github.NewClient(&http.Client{Transport: itr}), nil
  146. }