preview.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. package actions
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "strings"
  7. "github.com/google/go-github/v41/github"
  8. "gopkg.in/yaml.v2"
  9. )
  10. type EnvOpts struct {
  11. Client *github.Client
  12. ServerURL string
  13. PorterToken string
  14. GitRepoOwner, GitRepoName string
  15. EnvironmentName string
  16. ProjectID, ClusterID, GitInstallationID uint
  17. }
  18. func SetupEnv(opts *EnvOpts) error {
  19. // make a best-effort to create a Github environment. this is a non-fatal operation,
  20. // as the environments API is not enabled for private repositories that don't have
  21. // github enterprise.
  22. _, resp, err := opts.Client.Repositories.GetEnvironment(
  23. context.Background(),
  24. opts.GitRepoOwner,
  25. opts.GitRepoName,
  26. opts.EnvironmentName,
  27. )
  28. if resp != nil && resp.StatusCode == http.StatusNotFound {
  29. opts.Client.Repositories.CreateUpdateEnvironment(
  30. context.Background(),
  31. opts.GitRepoOwner,
  32. opts.GitRepoName,
  33. opts.EnvironmentName,
  34. nil,
  35. )
  36. }
  37. // create porter token secret
  38. err = createGithubSecret(
  39. opts.Client,
  40. getPorterTokenSecretName(opts.ProjectID),
  41. opts.PorterToken,
  42. opts.GitRepoOwner,
  43. opts.GitRepoName,
  44. )
  45. if err != nil {
  46. return err
  47. }
  48. // get the repository to find the default branch
  49. repo, _, err := opts.Client.Repositories.Get(
  50. context.TODO(),
  51. opts.GitRepoOwner,
  52. opts.GitRepoName,
  53. )
  54. if err != nil {
  55. return err
  56. }
  57. defaultBranch := repo.GetDefaultBranch()
  58. applyWorkflowYAML, err := getPreviewApplyActionYAML(opts)
  59. if err != nil {
  60. return err
  61. }
  62. deleteWorkflowYAML, err := getPreviewDeleteActionYAML(opts)
  63. if err != nil {
  64. return err
  65. }
  66. _, err = commitGithubFile(
  67. opts.Client,
  68. fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
  69. applyWorkflowYAML,
  70. opts.GitRepoOwner,
  71. opts.GitRepoName,
  72. defaultBranch,
  73. false,
  74. )
  75. if err != nil {
  76. if strings.Contains(err.Error(), "409 Could not create file") {
  77. // possibly a write-protected branch
  78. err = createPorterPreviewBranch(opts, defaultBranch)
  79. if err != nil {
  80. return fmt.Errorf("write-protected branch %s. Error creating porter-preview branch: %w", defaultBranch, err)
  81. }
  82. _, err = commitGithubFile(
  83. opts.Client,
  84. fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
  85. applyWorkflowYAML,
  86. opts.GitRepoOwner,
  87. opts.GitRepoName,
  88. "porter-preview",
  89. false,
  90. )
  91. if err != nil {
  92. return fmt.Errorf("write-protected branch %s. Error committing to porter-preview branch: %w", defaultBranch, err)
  93. }
  94. _, err = commitGithubFile(
  95. opts.Client,
  96. fmt.Sprintf("porter_%s_delete_env.yml", strings.ToLower(opts.EnvironmentName)),
  97. deleteWorkflowYAML,
  98. opts.GitRepoOwner,
  99. opts.GitRepoName,
  100. "porter-preview",
  101. false,
  102. )
  103. if err != nil {
  104. return fmt.Errorf("write-protected branch %s. Error committing to porter-preview branch: %w", defaultBranch, err)
  105. }
  106. pr, _, err := opts.Client.PullRequests.Create(
  107. context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.NewPullRequest{
  108. Title: github.String("Merge Porter preview environment Github Actions workflow files"),
  109. Base: github.String(defaultBranch),
  110. Head: github.String("porter-preview"),
  111. },
  112. )
  113. if err != nil {
  114. return err
  115. }
  116. return fmt.Errorf("write-protected branch %s. Please merge %s to enable preview environment for your repository", defaultBranch, pr.GetURL())
  117. }
  118. return err
  119. }
  120. _, err = commitGithubFile(
  121. opts.Client,
  122. fmt.Sprintf("porter_%s_delete_env.yml", strings.ToLower(opts.EnvironmentName)),
  123. deleteWorkflowYAML,
  124. opts.GitRepoOwner,
  125. opts.GitRepoName,
  126. defaultBranch,
  127. false,
  128. )
  129. if err != nil {
  130. return err
  131. }
  132. return err
  133. }
  134. func DeleteEnv(opts *EnvOpts) error {
  135. // get the repository to find the default branch
  136. repo, _, err := opts.Client.Repositories.Get(
  137. context.TODO(),
  138. opts.GitRepoOwner,
  139. opts.GitRepoName,
  140. )
  141. if err != nil {
  142. return err
  143. }
  144. defaultBranch := repo.GetDefaultBranch()
  145. // delete GitHub Environment: check that environment exists before deletion
  146. _, resp, err := opts.Client.Repositories.GetEnvironment(
  147. context.Background(),
  148. opts.GitRepoOwner,
  149. opts.GitRepoName,
  150. opts.EnvironmentName,
  151. )
  152. if err == nil && resp != nil && resp.StatusCode == http.StatusOK {
  153. _, err = opts.Client.Repositories.DeleteEnvironment(
  154. context.Background(),
  155. opts.GitRepoOwner,
  156. opts.GitRepoName,
  157. opts.EnvironmentName,
  158. )
  159. if err != nil {
  160. return err
  161. }
  162. }
  163. err = deleteGithubFile(
  164. opts.Client,
  165. fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
  166. opts.GitRepoOwner,
  167. opts.GitRepoName,
  168. defaultBranch,
  169. false,
  170. )
  171. if err != nil {
  172. return err
  173. }
  174. return deleteGithubFile(
  175. opts.Client,
  176. fmt.Sprintf("porter_%s_delete_env.yml", strings.ToLower(opts.EnvironmentName)),
  177. opts.GitRepoOwner,
  178. opts.GitRepoName,
  179. defaultBranch,
  180. false,
  181. )
  182. }
  183. func getPreviewApplyActionYAML(opts *EnvOpts) ([]byte, error) {
  184. gaSteps := []GithubActionYAMLStep{
  185. getCheckoutCodeStep(),
  186. getCreatePreviewEnvStep(
  187. opts.ServerURL,
  188. getPorterTokenSecretName(opts.ProjectID),
  189. opts.ProjectID,
  190. opts.ClusterID,
  191. opts.GitInstallationID,
  192. opts.GitRepoName,
  193. "v0.1.0",
  194. ),
  195. }
  196. actionYAML := GithubActionYAML{
  197. On: []string{"pull_request"},
  198. Name: "Porter Preview Environment",
  199. Jobs: map[string]GithubActionYAMLJob{
  200. "porter-preview": {
  201. RunsOn: "ubuntu-latest",
  202. Steps: gaSteps,
  203. },
  204. },
  205. }
  206. return yaml.Marshal(actionYAML)
  207. }
  208. func getPreviewDeleteActionYAML(opts *EnvOpts) ([]byte, error) {
  209. gaSteps := []GithubActionYAMLStep{
  210. getDeletePreviewEnvStep(
  211. opts.ServerURL,
  212. getPorterTokenSecretName(opts.ProjectID),
  213. opts.ProjectID,
  214. opts.ClusterID,
  215. opts.GitInstallationID,
  216. opts.GitRepoName,
  217. "v0.1.0",
  218. ),
  219. }
  220. actionYAML := GithubActionYAML{
  221. On: map[string]interface{}{
  222. "pull_request": map[string]interface{}{
  223. "types": []string{"closed"},
  224. },
  225. },
  226. Name: "Porter Preview Environment",
  227. Jobs: map[string]GithubActionYAMLJob{
  228. "porter-delete-preview": {
  229. RunsOn: "ubuntu-latest",
  230. Steps: gaSteps,
  231. },
  232. },
  233. }
  234. return yaml.Marshal(actionYAML)
  235. }
  236. func createPorterPreviewBranch(opts *EnvOpts, defaultBranch string) error {
  237. _, resp, err := opts.Client.Repositories.GetBranch(
  238. context.Background(), opts.GitRepoOwner, opts.GitRepoName, "porter-preview", false,
  239. )
  240. if resp.StatusCode == http.StatusNotFound {
  241. branch, _, err := opts.Client.Repositories.GetBranch(
  242. context.Background(), opts.GitRepoOwner, opts.GitRepoName, defaultBranch, false,
  243. )
  244. if err != nil {
  245. return err
  246. }
  247. _, _, err = opts.Client.Git.CreateRef(
  248. context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.Reference{
  249. Ref: github.String("refs/heads/porter-preview"),
  250. Object: &github.GitObject{
  251. SHA: branch.Commit.SHA,
  252. },
  253. },
  254. )
  255. if err != nil {
  256. return err
  257. }
  258. return nil
  259. }
  260. if err != nil {
  261. return err
  262. }
  263. return nil
  264. }