github.go 4.2 KB

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