github_incoming.go 4.7 KB

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