actions.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. package actions
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "github.com/google/go-github/v33/github"
  7. "github.com/porter-dev/porter/internal/models"
  8. "github.com/porter-dev/porter/internal/repository"
  9. "golang.org/x/crypto/nacl/box"
  10. "golang.org/x/oauth2"
  11. "strings"
  12. "gopkg.in/yaml.v2"
  13. )
  14. type GithubActions struct {
  15. GitIntegration *models.GitRepo
  16. GitRepoName string
  17. GitRepoOwner string
  18. Repo repository.Repository
  19. GithubConf *oauth2.Config
  20. WebhookToken string
  21. PorterToken string
  22. ProjectID uint
  23. ReleaseName string
  24. DockerFilePath string
  25. ImageRepoURL string
  26. defaultBranch string
  27. }
  28. func (g *GithubActions) Setup() (string, error) {
  29. client, err := g.getClient()
  30. if err != nil {
  31. return "", err
  32. }
  33. // get the repository to find the default branch
  34. repo, _, err := client.Repositories.Get(
  35. context.TODO(),
  36. g.GitRepoOwner,
  37. g.GitRepoName,
  38. )
  39. if err != nil {
  40. return "", err
  41. }
  42. g.defaultBranch = repo.GetDefaultBranch()
  43. // create a new secret with a webhook token
  44. err = g.createGithubSecret(client, g.getWebhookSecretName(), g.WebhookToken)
  45. if err != nil {
  46. return "", err
  47. }
  48. // create a new secret with a porter token
  49. err = g.createGithubSecret(client, g.getPorterTokenSecretName(), g.PorterToken)
  50. if err != nil {
  51. return "", err
  52. }
  53. fileBytes, err := g.GetGithubActionYAML()
  54. if err != nil {
  55. return "", err
  56. }
  57. return g.commitGithubFile(client, fileBytes)
  58. }
  59. type GithubActionYAMLStep struct {
  60. Name string `yaml:"name"`
  61. ID string `yaml:"id"`
  62. Run string `yaml:"run"`
  63. }
  64. type GithubActionYAMLOnPushBranches struct {
  65. Branches []string `yaml:"branches"`
  66. }
  67. type GithubActionYAMLOnPush struct {
  68. Push GithubActionYAMLOnPushBranches `yaml:"push"`
  69. }
  70. type GithubActionYAMLJob struct {
  71. RunsOn string `yaml:"runs-on"`
  72. Steps []GithubActionYAMLStep `yaml:"steps"`
  73. }
  74. type GithubActionYAML struct {
  75. On GithubActionYAMLOnPush `yaml:"on"`
  76. Name string `yaml:"name"`
  77. Jobs map[string]GithubActionYAMLJob `yaml:"jobs"`
  78. }
  79. func (g *GithubActions) GetGithubActionYAML() ([]byte, error) {
  80. actionYAML := &GithubActionYAML{
  81. On: GithubActionYAMLOnPush{
  82. Push: GithubActionYAMLOnPushBranches{
  83. Branches: []string{
  84. g.defaultBranch,
  85. },
  86. },
  87. },
  88. Name: "Deploy to Porter",
  89. Jobs: map[string]GithubActionYAMLJob{
  90. "porter-deploy": GithubActionYAMLJob{
  91. RunsOn: "ubuntu-latest",
  92. Steps: []GithubActionYAMLStep{
  93. getDownloadPorterStep(),
  94. getConfigurePorterStep(g.getPorterTokenSecretName()),
  95. getDockerBuildPushStep(g.DockerFilePath, g.ImageRepoURL),
  96. deployPorterWebhookStep(g.getWebhookSecretName(), g.ImageRepoURL),
  97. },
  98. },
  99. },
  100. }
  101. return yaml.Marshal(actionYAML)
  102. }
  103. func (g *GithubActions) getClient() (*github.Client, error) {
  104. // get the oauth integration
  105. oauthInt, err := g.Repo.OAuthIntegration.ReadOAuthIntegration(g.GitIntegration.OAuthIntegrationID)
  106. if err != nil {
  107. return nil, err
  108. }
  109. tok := &oauth2.Token{
  110. AccessToken: string(oauthInt.AccessToken),
  111. RefreshToken: string(oauthInt.RefreshToken),
  112. TokenType: "Bearer",
  113. }
  114. client := github.NewClient(g.GithubConf.Client(oauth2.NoContext, tok))
  115. return client, nil
  116. }
  117. func (g *GithubActions) createGithubSecret(
  118. client *github.Client,
  119. secretName,
  120. secretValue string,
  121. ) error {
  122. // get the public key for the repo
  123. key, _, err := client.Actions.GetRepoPublicKey(context.TODO(), g.GitRepoOwner, g.GitRepoName)
  124. if err != nil {
  125. return err
  126. }
  127. // encrypt the secret with the public key
  128. keyBytes := [32]byte{}
  129. keyDecoded, err := base64.StdEncoding.DecodeString(*key.Key)
  130. if err != nil {
  131. return err
  132. }
  133. copy(keyBytes[:], keyDecoded[:])
  134. secretEncoded, err := box.SealAnonymous(nil, []byte(secretValue), &keyBytes, nil)
  135. if err != nil {
  136. return err
  137. }
  138. encrypted := base64.StdEncoding.EncodeToString(secretEncoded)
  139. encryptedSecret := &github.EncryptedSecret{
  140. Name: secretName,
  141. KeyID: *key.KeyID,
  142. EncryptedValue: encrypted,
  143. }
  144. // write the secret to the repo
  145. _, err = client.Actions.CreateOrUpdateRepoSecret(context.TODO(), g.GitRepoOwner, g.GitRepoName, encryptedSecret)
  146. return nil
  147. }
  148. func (g *GithubActions) getWebhookSecretName() string {
  149. return fmt.Sprintf("WEBHOOK_%s", strings.Replace(
  150. strings.ToUpper(g.ReleaseName), "-", "_", -1),
  151. )
  152. }
  153. func (g *GithubActions) getPorterTokenSecretName() string {
  154. return fmt.Sprintf("PORTER_TOKEN_%d", g.ProjectID)
  155. }
  156. func (g *GithubActions) commitGithubFile(
  157. client *github.Client,
  158. contents []byte,
  159. ) (string, error) {
  160. fmt.Println("GITHUB ACTION CONTENTS ARE", string(contents))
  161. opts := &github.RepositoryContentFileOptions{
  162. Message: github.String("Create porter.yml file"),
  163. Content: contents,
  164. Branch: github.String(g.defaultBranch),
  165. Committer: &github.CommitAuthor{
  166. Name: github.String("Porter Bot"),
  167. Email: github.String("contact@getporter.dev"),
  168. },
  169. }
  170. resp, _, err := client.Repositories.CreateFile(
  171. context.TODO(),
  172. g.GitRepoOwner,
  173. g.GitRepoName,
  174. ".github/workflows/porter.yml",
  175. opts,
  176. )
  177. if err != nil {
  178. return "", err
  179. }
  180. return *resp.Commit.SHA, nil
  181. }