stack.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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. // GithubPRAction_ManualPreviewDeploy is the action for manually deploying a preview environment
  19. GithubPRAction_ManualPreviewDeploy GithubPRAction = "manual-preview-deploy"
  20. )
  21. type GithubPROpts struct {
  22. PRAction GithubPRAction
  23. Client *github.Client
  24. GitRepoOwner, GitRepoName string
  25. ApplyWorkflowYAML string
  26. StackName string
  27. ProjectID, ClusterID uint
  28. ServerURL string
  29. DefaultBranch string
  30. SecretName string
  31. PorterYamlPath string
  32. Body string
  33. WorkflowFileName string
  34. PRBranch string
  35. DeploymentTargetId string
  36. }
  37. type GetStackApplyActionYAMLOpts struct {
  38. ServerURL string
  39. StackName string
  40. ProjectID, ClusterID uint
  41. DefaultBranch string
  42. SecretName string
  43. PorterYamlPath string
  44. Preview bool
  45. DeploymentTargetId string
  46. }
  47. func OpenGithubPR(opts *GithubPROpts) (*github.PullRequest, error) {
  48. var pr *github.PullRequest
  49. if opts == nil {
  50. return pr, fmt.Errorf("input options cannot be nil")
  51. }
  52. err := createNewBranch(opts.Client,
  53. opts.GitRepoOwner,
  54. opts.GitRepoName,
  55. opts.DefaultBranch,
  56. opts.PRBranch,
  57. )
  58. if err != nil {
  59. return pr, fmt.Errorf("error creating branch: %w", err)
  60. }
  61. err = commitChange(opts.PRBranch, *opts)
  62. if err != nil {
  63. return pr, fmt.Errorf("error committing change: %w", err)
  64. }
  65. prTitle := getPRTitle(opts.PRAction, opts.StackName)
  66. pr, _, err = opts.Client.PullRequests.Create(
  67. context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.NewPullRequest{
  68. Title: github.String(prTitle),
  69. Base: github.String(opts.DefaultBranch),
  70. Head: github.String(opts.PRBranch),
  71. Body: github.String(opts.Body),
  72. },
  73. )
  74. if err != nil {
  75. return pr, fmt.Errorf(
  76. "error creating PR: %w",
  77. err,
  78. )
  79. }
  80. return pr, nil
  81. }
  82. func getPRTitle(action GithubPRAction, stackName string) string {
  83. switch action {
  84. case GithubPRAction_NewAppWorkflow:
  85. return fmt.Sprintf("Enable Porter Application %s", stackName)
  86. case GithubPRAction_DeleteAppWorkflow:
  87. return fmt.Sprintf("Delete Porter Application %s", stackName)
  88. case GithubPRAction_PreviewAppWorkflow:
  89. return "Enable Preview Environments on Porter"
  90. case GithubPRAction_ManualPreviewDeploy:
  91. return "Deploy Preview Environments Manually"
  92. default:
  93. return ""
  94. }
  95. }
  96. func commitChange(prBranchName string, opts GithubPROpts) error {
  97. switch opts.PRAction {
  98. case GithubPRAction_NewAppWorkflow:
  99. applyWorkflowYAML, err := getStackApplyActionYAML(&GetStackApplyActionYAMLOpts{
  100. ServerURL: opts.ServerURL,
  101. ClusterID: opts.ClusterID,
  102. ProjectID: opts.ProjectID,
  103. StackName: opts.StackName,
  104. DefaultBranch: opts.DefaultBranch,
  105. SecretName: opts.SecretName,
  106. PorterYamlPath: opts.PorterYamlPath,
  107. DeploymentTargetId: opts.DeploymentTargetId,
  108. Preview: false,
  109. })
  110. if err != nil {
  111. return err
  112. }
  113. _, err = commitWorkflowFile(
  114. opts.Client,
  115. fmt.Sprintf("porter_stack_%s.yml", strings.ToLower(opts.StackName)),
  116. applyWorkflowYAML, opts.GitRepoOwner,
  117. opts.GitRepoName, prBranchName, false,
  118. )
  119. if err != nil {
  120. return fmt.Errorf("error committing file: %w", err)
  121. }
  122. return nil
  123. case GithubPRAction_DeleteAppWorkflow:
  124. err := deleteGithubFile(
  125. opts.Client,
  126. opts.WorkflowFileName,
  127. opts.GitRepoOwner,
  128. opts.GitRepoName,
  129. prBranchName,
  130. false,
  131. )
  132. if err != nil {
  133. return fmt.Errorf("error committing deletion: %w", err)
  134. }
  135. return nil
  136. case GithubPRAction_PreviewAppWorkflow:
  137. previewWorkflowYAML, err := getStackApplyActionYAML(&GetStackApplyActionYAMLOpts{
  138. ServerURL: opts.ServerURL,
  139. ClusterID: opts.ClusterID,
  140. ProjectID: opts.ProjectID,
  141. StackName: opts.StackName,
  142. DefaultBranch: opts.DefaultBranch,
  143. SecretName: opts.SecretName,
  144. PorterYamlPath: opts.PorterYamlPath,
  145. Preview: true,
  146. })
  147. if err != nil {
  148. return err
  149. }
  150. _, err = commitWorkflowFile(
  151. opts.Client,
  152. fmt.Sprintf("porter_preview_%s.yml", strings.ToLower(opts.StackName)),
  153. previewWorkflowYAML, opts.GitRepoOwner,
  154. opts.GitRepoName, prBranchName, false,
  155. )
  156. if err != nil {
  157. return fmt.Errorf("error committing file: %w", err)
  158. }
  159. return nil
  160. case GithubPRAction_ManualPreviewDeploy:
  161. manualPreviewWorkflowYaml, err := getManualPreviewActionYAML(&GetStackApplyActionYAMLOpts{
  162. ServerURL: opts.ServerURL,
  163. ClusterID: opts.ClusterID,
  164. ProjectID: opts.ProjectID,
  165. StackName: opts.StackName,
  166. DefaultBranch: opts.DefaultBranch,
  167. SecretName: opts.SecretName,
  168. PorterYamlPath: opts.PorterYamlPath,
  169. DeploymentTargetId: opts.DeploymentTargetId,
  170. Preview: true,
  171. })
  172. if err != nil {
  173. return err
  174. }
  175. _, err = commitWorkflowFile(
  176. opts.Client,
  177. fmt.Sprintf("porter_manual_preview_%s.yml", strings.ToLower(opts.StackName)),
  178. manualPreviewWorkflowYaml, opts.GitRepoOwner,
  179. opts.GitRepoName, prBranchName, false,
  180. )
  181. if err != nil {
  182. return fmt.Errorf("error committing file: %w", err)
  183. }
  184. return nil
  185. default:
  186. return fmt.Errorf("invalid PR action: %s", opts.PRAction)
  187. }
  188. }
  189. func getManualPreviewActionYAML(opts *GetStackApplyActionYAMLOpts) ([]byte, error) {
  190. checkoutWithUsesRef := GithubActionYAMLStep{
  191. Name: "Checkout code",
  192. Uses: "actions/checkout@v3",
  193. With: map[string]string{
  194. "ref": "${{ github.event.inputs.branch }}",
  195. },
  196. }
  197. gaSteps := []GithubActionYAMLStep{
  198. checkoutWithUsesRef,
  199. getSetTagStep(),
  200. getSetupPorterStep(),
  201. getDeployStackStep(
  202. opts.ServerURL,
  203. opts.SecretName,
  204. opts.StackName,
  205. "v0.1.0",
  206. opts.PorterYamlPath,
  207. opts.ProjectID,
  208. opts.ClusterID,
  209. opts.DeploymentTargetId,
  210. true,
  211. ),
  212. }
  213. actionYAML := GithubActionYAML{
  214. On: map[string]interface{}{
  215. "workflow_dispatch": map[string]interface{}{
  216. "inputs": map[string]interface{}{
  217. "branch": map[string]interface{}{
  218. "description": "Base branch tod deploy",
  219. "type": "string",
  220. "required": true,
  221. },
  222. },
  223. },
  224. },
  225. Name: fmt.Sprintf("Deploy Preview for %s", opts.StackName),
  226. Jobs: map[string]GithubActionYAMLJob{
  227. "porter-deploy": {
  228. RunsOn: "ubuntu-latest",
  229. Steps: gaSteps,
  230. },
  231. },
  232. }
  233. by, err := yaml.Marshal(actionYAML)
  234. if err != nil {
  235. return nil, fmt.Errorf("error marshalling yaml: %w", err)
  236. }
  237. return by, nil
  238. }
  239. func getStackApplyActionYAML(opts *GetStackApplyActionYAMLOpts) ([]byte, error) {
  240. gaSteps := []GithubActionYAMLStep{
  241. getCheckoutCodeStep(),
  242. getSetTagStep(),
  243. getSetupPorterStep(),
  244. getDeployStackStep(
  245. opts.ServerURL,
  246. opts.SecretName,
  247. opts.StackName,
  248. "v0.1.0",
  249. opts.PorterYamlPath,
  250. opts.ProjectID,
  251. opts.ClusterID,
  252. opts.DeploymentTargetId,
  253. opts.Preview,
  254. ),
  255. }
  256. if opts.Preview {
  257. actionYaml := GithubActionYAML{
  258. On: GithubActionYAMLOnPullRequest{
  259. PullRequest: GithubActionYAMLOnPullRequestTypes{
  260. Paths: []string{
  261. "**",
  262. "!.github/workflows/porter_**",
  263. },
  264. Branches: []string{
  265. opts.DefaultBranch,
  266. },
  267. Types: []string{
  268. "opened",
  269. "synchronize",
  270. },
  271. },
  272. },
  273. Name: "Deploy to Preview Environment",
  274. Jobs: map[string]GithubActionYAMLJob{
  275. "porter-deploy": {
  276. RunsOn: "ubuntu-latest",
  277. Steps: gaSteps,
  278. },
  279. },
  280. }
  281. return yaml.Marshal(actionYaml)
  282. }
  283. actionYAML := GithubActionYAML{
  284. On: GithubActionYAMLOnPush{
  285. Push: GithubActionYAMLOnPushBranches{
  286. Branches: []string{
  287. opts.DefaultBranch,
  288. },
  289. },
  290. },
  291. Name: fmt.Sprintf("Deploy to %s", opts.StackName),
  292. Jobs: map[string]GithubActionYAMLJob{
  293. "porter-deploy": {
  294. RunsOn: "ubuntu-latest",
  295. Steps: gaSteps,
  296. },
  297. },
  298. }
  299. return yaml.Marshal(actionYAML)
  300. }