github.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package porter_app
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "github.com/bradleyfalzon/ghinstallation/v2"
  9. "github.com/google/go-github/v39/github"
  10. "github.com/google/uuid"
  11. "github.com/porter-dev/porter/internal/models"
  12. "github.com/porter-dev/porter/internal/repository"
  13. "github.com/porter-dev/porter/internal/telemetry"
  14. )
  15. // CreateAppWebhookInput is the input to the CreateAppWebhook function
  16. type CreateAppWebhookInput struct {
  17. ProjectID uint
  18. ClusterID uint
  19. PorterAppName string
  20. GithubAppSecret []byte
  21. GithubAppID string
  22. GithubWebhookSecret string
  23. ServerURL string
  24. PorterAppRepository repository.PorterAppRepository
  25. GithubWebhookRepository repository.GithubWebhookRepository
  26. }
  27. // CreateAppWebhook creates or updates a github webhook for a porter app associated with a given project / cluster / app
  28. // The webhook watches for pull request and push events, used for managing preview environments
  29. func CreateAppWebhook(ctx context.Context, inp CreateAppWebhookInput) error {
  30. ctx, span := telemetry.NewSpan(ctx, "porter-app-create-app-webhook")
  31. defer span.End()
  32. if inp.PorterAppName == "" {
  33. return telemetry.Error(ctx, span, nil, "porter app name is empty")
  34. }
  35. if inp.ProjectID == 0 {
  36. return telemetry.Error(ctx, span, nil, "project id is empty")
  37. }
  38. if inp.ClusterID == 0 {
  39. return telemetry.Error(ctx, span, nil, "cluster id is empty")
  40. }
  41. if inp.GithubAppSecret == nil {
  42. return telemetry.Error(ctx, span, nil, "github app secret is nil")
  43. }
  44. if inp.GithubAppID == "" {
  45. return telemetry.Error(ctx, span, nil, "github app id is empty")
  46. }
  47. if inp.GithubWebhookSecret == "" {
  48. return telemetry.Error(ctx, span, nil, "github webhook secret is empty")
  49. }
  50. if inp.PorterAppRepository == nil {
  51. return telemetry.Error(ctx, span, nil, "porter app repository is nil")
  52. }
  53. if inp.GithubWebhookRepository == nil {
  54. return telemetry.Error(ctx, span, nil, "github webhook repository is nil")
  55. }
  56. porterApp, err := inp.PorterAppRepository.ReadPorterAppByName(inp.ClusterID, inp.PorterAppName)
  57. if err != nil {
  58. return telemetry.Error(ctx, span, err, "could not read porter app by name")
  59. }
  60. if porterApp.ID == 0 {
  61. return telemetry.Error(ctx, span, nil, "porter app not found")
  62. }
  63. if porterApp.GitRepoID == 0 {
  64. return telemetry.Error(ctx, span, nil, "porter app git repo id is empty")
  65. }
  66. githubClient, err := getGithubClientByRepoID(ctx, porterApp.GitRepoID, inp.GithubAppSecret, inp.GithubAppID)
  67. if err != nil {
  68. return telemetry.Error(ctx, span, err, "error creating github client")
  69. }
  70. repoDetails := strings.Split(porterApp.RepoName, "/")
  71. if len(repoDetails) != 2 {
  72. return telemetry.Error(ctx, span, nil, "repo name is not in the format <org>/<repo>")
  73. }
  74. if _, _, err := githubClient.Repositories.Get(ctx, repoDetails[0], repoDetails[1]); err != nil {
  75. return telemetry.Error(ctx, span, err, "error getting github repo")
  76. }
  77. hook := &github.Hook{
  78. Config: map[string]interface{}{
  79. "content_type": "json",
  80. "secret": inp.GithubWebhookSecret,
  81. },
  82. Events: []string{"pull_request", "push"},
  83. Active: github.Bool(true),
  84. }
  85. // check if the webhook already exists
  86. webhook, err := inp.GithubWebhookRepository.GetByClusterAndAppID(ctx, inp.ClusterID, porterApp.ID)
  87. if err != nil {
  88. return telemetry.Error(ctx, span, err, "error getting github webhook")
  89. }
  90. if webhook.ID != uuid.Nil {
  91. hook.Config["url"] = fmt.Sprintf("%s/api/webhooks/github/%s", inp.ServerURL, webhook.ID.String())
  92. _, _, err := githubClient.Repositories.EditHook(ctx, repoDetails[0], repoDetails[1], webhook.GithubWebhookID, hook)
  93. if err != nil {
  94. return telemetry.Error(ctx, span, err, "error editing github webhook")
  95. }
  96. return nil
  97. }
  98. webhookID := uuid.New()
  99. hook.Config["url"] = fmt.Sprintf("%s/api/webhooks/github/%s", inp.ServerURL, webhookID)
  100. hook, _, err = githubClient.Repositories.CreateHook(ctx, repoDetails[0], repoDetails[1], hook)
  101. if err != nil {
  102. return telemetry.Error(ctx, span, err, "error creating github webhook")
  103. }
  104. webhook = &models.GithubWebhook{
  105. ID: webhookID,
  106. ProjectID: int(porterApp.ProjectID),
  107. ClusterID: int(porterApp.ClusterID),
  108. PorterAppID: int(porterApp.ID),
  109. GithubWebhookID: hook.GetID(),
  110. }
  111. webhook, err = inp.GithubWebhookRepository.Insert(ctx, webhook)
  112. if err != nil {
  113. return telemetry.Error(ctx, span, err, "error saving github webhook")
  114. }
  115. return nil
  116. }
  117. func getGithubClientByRepoID(ctx context.Context, repoID uint, githubAppSecret []byte, githubAppID string) (*github.Client, error) {
  118. ctx, span := telemetry.NewSpan(ctx, "get-github-client-by-repo-id")
  119. defer span.End()
  120. if githubAppSecret == nil {
  121. return nil, telemetry.Error(ctx, span, nil, "github app secret is nil")
  122. }
  123. if githubAppID == "" {
  124. return nil, telemetry.Error(ctx, span, nil, "github app id is empty")
  125. }
  126. appID, err := strconv.Atoi(githubAppID)
  127. if err != nil {
  128. return nil, telemetry.Error(ctx, span, err, "could not convert github app id to int")
  129. }
  130. itr, err := ghinstallation.New(
  131. http.DefaultTransport,
  132. int64(appID),
  133. int64(repoID),
  134. githubAppSecret,
  135. )
  136. if err != nil {
  137. return nil, telemetry.Error(ctx, span, err, "could not create github app client")
  138. }
  139. if itr == nil {
  140. return nil, telemetry.Error(ctx, span, nil, "github app client is nil")
  141. }
  142. return github.NewClient(&http.Client{Transport: itr}), nil
  143. }