stack.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. package actions
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "github.com/google/go-github/v41/github"
  7. "gopkg.in/yaml.v2"
  8. )
  9. // GithubPRAction is an action to take when opening a PR
  10. type GithubPRAction string
  11. const (
  12. // GithubPRAction_NewAppWorkflow is the action for creating a workflow for a new application
  13. GithubPRAction_NewAppWorkflow GithubPRAction = "new-app-workflow"
  14. // GithubPRAction_DeleteAppWorkflow is the action for deleting an application workflow
  15. GithubPRAction_DeleteAppWorkflow GithubPRAction = "delete-app-workflow"
  16. // GithubPRAction_PreviewAppWorkflow is the action for creating the preview app workflow
  17. GithubPRAction_PreviewAppWorkflow GithubPRAction = "preview-app-workflow"
  18. )
  19. type GithubPROpts struct {
  20. PRAction GithubPRAction
  21. Client *github.Client
  22. GitRepoOwner, GitRepoName string
  23. ApplyWorkflowYAML string
  24. StackName string
  25. ProjectID, ClusterID uint
  26. ServerURL string
  27. DefaultBranch string
  28. SecretName string
  29. PorterYamlPath string
  30. Body string
  31. WorkflowFileName string
  32. PRBranch string
  33. DeploymentTargetId string
  34. }
  35. type GetStackApplyActionYAMLOpts struct {
  36. ServerURL string
  37. StackName string
  38. ProjectID, ClusterID uint
  39. DefaultBranch string
  40. SecretName string
  41. PorterYamlPath string
  42. Preview bool
  43. DeploymentTargetId string
  44. }
  45. func OpenGithubPR(opts *GithubPROpts) (*github.PullRequest, error) {
  46. var pr *github.PullRequest
  47. if opts == nil {
  48. return pr, fmt.Errorf("input options cannot be nil")
  49. }
  50. err := createNewBranch(opts.Client,
  51. opts.GitRepoOwner,
  52. opts.GitRepoName,
  53. opts.DefaultBranch,
  54. opts.PRBranch,
  55. )
  56. if err != nil {
  57. return pr, fmt.Errorf("error creating branch: %w", err)
  58. }
  59. err = commitChange(opts.PRBranch, *opts)
  60. if err != nil {
  61. return pr, fmt.Errorf("error committing change: %w", err)
  62. }
  63. prTitle := getPRTitle(opts.PRAction, opts.StackName)
  64. pr, _, err = opts.Client.PullRequests.Create(
  65. context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.NewPullRequest{
  66. Title: github.String(prTitle),
  67. Base: github.String(opts.DefaultBranch),
  68. Head: github.String(opts.PRBranch),
  69. Body: github.String(opts.Body),
  70. },
  71. )
  72. if err != nil {
  73. return pr, fmt.Errorf(
  74. "error creating PR: %w",
  75. err,
  76. )
  77. }
  78. return pr, nil
  79. }
  80. func getPRTitle(action GithubPRAction, stackName string) string {
  81. switch action {
  82. case GithubPRAction_NewAppWorkflow:
  83. return fmt.Sprintf("Enable Porter Application %s", stackName)
  84. case GithubPRAction_DeleteAppWorkflow:
  85. return fmt.Sprintf("Delete Porter Application %s", stackName)
  86. case GithubPRAction_PreviewAppWorkflow:
  87. return "Enable Preview Environments on Porter"
  88. default:
  89. return ""
  90. }
  91. }
  92. func commitChange(prBranchName string, opts GithubPROpts) error {
  93. switch opts.PRAction {
  94. case GithubPRAction_NewAppWorkflow:
  95. applyWorkflowYAML, err := getStackApplyActionYAML(&GetStackApplyActionYAMLOpts{
  96. ServerURL: opts.ServerURL,
  97. ClusterID: opts.ClusterID,
  98. ProjectID: opts.ProjectID,
  99. StackName: opts.StackName,
  100. DefaultBranch: opts.DefaultBranch,
  101. SecretName: opts.SecretName,
  102. PorterYamlPath: opts.PorterYamlPath,
  103. DeploymentTargetId: opts.DeploymentTargetId,
  104. Preview: false,
  105. })
  106. if err != nil {
  107. return err
  108. }
  109. _, err = commitWorkflowFile(
  110. opts.Client,
  111. fmt.Sprintf("porter_stack_%s.yml", strings.ToLower(opts.StackName)),
  112. applyWorkflowYAML, opts.GitRepoOwner,
  113. opts.GitRepoName, prBranchName, false,
  114. )
  115. if err != nil {
  116. return fmt.Errorf("error committing file: %w", err)
  117. }
  118. return nil
  119. case GithubPRAction_DeleteAppWorkflow:
  120. err := deleteGithubFile(
  121. opts.Client,
  122. opts.WorkflowFileName,
  123. opts.GitRepoOwner,
  124. opts.GitRepoName,
  125. prBranchName,
  126. false,
  127. )
  128. if err != nil {
  129. return fmt.Errorf("error committing deletion: %w", err)
  130. }
  131. return nil
  132. case GithubPRAction_PreviewAppWorkflow:
  133. previewWorkflowYAML, err := getStackApplyActionYAML(&GetStackApplyActionYAMLOpts{
  134. ServerURL: opts.ServerURL,
  135. ClusterID: opts.ClusterID,
  136. ProjectID: opts.ProjectID,
  137. StackName: opts.StackName,
  138. DefaultBranch: opts.DefaultBranch,
  139. SecretName: opts.SecretName,
  140. PorterYamlPath: opts.PorterYamlPath,
  141. Preview: true,
  142. })
  143. if err != nil {
  144. return err
  145. }
  146. _, err = commitWorkflowFile(
  147. opts.Client,
  148. fmt.Sprintf("porter_preview_%s.yml", strings.ToLower(opts.StackName)),
  149. previewWorkflowYAML, opts.GitRepoOwner,
  150. opts.GitRepoName, prBranchName, false,
  151. )
  152. if err != nil {
  153. return fmt.Errorf("error committing file: %w", err)
  154. }
  155. return nil
  156. default:
  157. return fmt.Errorf("invalid PR action: %s", opts.PRAction)
  158. }
  159. }
  160. func getStackApplyActionYAML(opts *GetStackApplyActionYAMLOpts) ([]byte, error) {
  161. gaSteps := []GithubActionYAMLStep{
  162. getCheckoutCodeStep(),
  163. getSetTagStep(),
  164. getSetupPorterStep(),
  165. getDeployStackStep(
  166. opts.ServerURL,
  167. opts.SecretName,
  168. opts.StackName,
  169. "v0.1.0",
  170. opts.PorterYamlPath,
  171. opts.ProjectID,
  172. opts.ClusterID,
  173. opts.DeploymentTargetId,
  174. opts.Preview,
  175. ),
  176. }
  177. if opts.Preview {
  178. actionYaml := GithubActionYAML{
  179. On: GithubActionYAMLOnPullRequest{
  180. PullRequest: GithubActionYAMLOnPullRequestTypes{
  181. Paths: []string{
  182. "**",
  183. "!.github/workflows/porter_**",
  184. },
  185. Branches: []string{
  186. opts.DefaultBranch,
  187. },
  188. Types: []string{
  189. "opened",
  190. "synchronize",
  191. },
  192. },
  193. },
  194. Name: "Deploy to Preview Environment",
  195. Jobs: map[string]GithubActionYAMLJob{
  196. "porter-deploy": {
  197. RunsOn: "ubuntu-latest",
  198. Steps: gaSteps,
  199. },
  200. },
  201. }
  202. return yaml.Marshal(actionYaml)
  203. }
  204. actionYAML := GithubActionYAML{
  205. On: GithubActionYAMLOnPush{
  206. Push: GithubActionYAMLOnPushBranches{
  207. Branches: []string{
  208. opts.DefaultBranch,
  209. },
  210. },
  211. },
  212. Name: fmt.Sprintf("Deploy to %s", opts.StackName),
  213. Jobs: map[string]GithubActionYAMLJob{
  214. "porter-deploy": {
  215. RunsOn: "ubuntu-latest",
  216. Steps: gaSteps,
  217. },
  218. },
  219. }
  220. return yaml.Marshal(actionYAML)
  221. }