Mohammed Nafees пре 3 година
родитељ
комит
f4c823650c

+ 13 - 14
api/server/handlers/environment/create.go

@@ -1,6 +1,7 @@
 package environment
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -134,8 +135,18 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	})
 
 	if err != nil {
-		c.deleteEnvAndReportError(w, r, env, err)
-		return
+		unwrappedErr := errors.Unwrap(err)
+
+		if unwrappedErr != nil {
+			if errors.Is(unwrappedErr, actions.ErrProtectedBranch) {
+				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
+			} else if errors.Is(unwrappedErr, actions.ErrCreatePRForProtectedBranch) {
+				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusPreconditionFailed))
+			}
+		} else {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
 	}
 
 	c.WriteResult(w, r, env.ToEnvironmentType())
@@ -151,18 +162,6 @@ func (c *CreateEnvironmentHandler) deleteEnvAndReportError(
 		return
 	}
 
-	if strings.Contains(err.Error(), "protected branch") {
-		if strings.Contains(err.Error(), "Please merge") {
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusOK))
-			return
-		}
-
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-			fmt.Errorf("Error creating preview environment workflow files on protected branch"), http.StatusConflict,
-		))
-		return
-	}
-
 	c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 }
 

+ 20 - 22
api/server/handlers/release/create.go

@@ -164,6 +164,15 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		}
 	}
 
+	if request.BuildConfig != nil {
+		_, err = createBuildConfig(c.Config(), release, request.BuildConfig)
+	}
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
 	if request.GitActionConfig != nil {
 		_, _, err := createGitAction(
 			c.Config(),
@@ -177,28 +186,21 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		)
 
 		if err != nil {
-			if errors.Is(err, actions.ErrProtectedBranch) {
-				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-					fmt.Errorf("Error creating github action workflows. Cannot write to protected branch: %s",
-						request.GitActionConfig.GitBranch), http.StatusConflict,
-				))
+			unwrappedErr := errors.Unwrap(err)
+
+			if unwrappedErr != nil {
+				if errors.Is(unwrappedErr, actions.ErrProtectedBranch) {
+					c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
+				} else if errors.Is(unwrappedErr, actions.ErrCreatePRForProtectedBranch) {
+					c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusPreconditionFailed))
+				}
+			} else {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 				return
 			}
-
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
 		}
 	}
 
-	if request.BuildConfig != nil {
-		_, err = createBuildConfig(c.Config(), release, request.BuildConfig)
-	}
-
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
 	c.Config().AnalyticsClient.Track(analytics.ApplicationLaunchSuccessTrack(
 		&analytics.ApplicationLaunchSuccessTrackOpts{
 			ApplicationScopedTrackOpts: analytics.GetApplicationScopedTrackOpts(
@@ -391,11 +393,7 @@ func createGitAction(
 		return nil, nil, err
 	}
 
-	if gitErr != nil {
-		return nil, nil, gitErr
-	}
-
-	return ga.ToGitActionConfigType(), workflowYAML, nil
+	return ga.ToGitActionConfigType(), workflowYAML, gitErr
 }
 
 func getToken(

+ 60 - 20
internal/integrations/ci/actions/actions.go

@@ -21,7 +21,10 @@ import (
 	"gopkg.in/yaml.v2"
 )
 
-var ErrProtectedBranch = errors.New("protected branch")
+var (
+	ErrProtectedBranch            = errors.New("protected branch")
+	ErrCreatePRForProtectedBranch = errors.New("unable to create PR to merge workflow files into protected branch")
+)
 
 type GithubActions struct {
 	ServerURL    string
@@ -80,23 +83,6 @@ func (g *GithubActions) Setup() ([]byte, error) {
 
 	g.defaultBranch = repo.GetDefaultBranch()
 
-	// check if the default branch is write-protected
-	branch, _, err := client.Repositories.GetBranch(
-		context.Background(),
-		g.GitRepoOwner,
-		g.GitRepoName,
-		g.defaultBranch,
-		true,
-	)
-
-	if err != nil {
-		return nil, err
-	}
-
-	if branch.GetProtected() {
-		return nil, ErrProtectedBranch
-	}
-
 	if !g.DryRun {
 		// create porter token secret
 		if err := createGithubSecret(client, g.getPorterTokenSecretName(), g.PorterToken, g.GitRepoOwner, g.GitRepoName); err != nil {
@@ -117,9 +103,63 @@ func (g *GithubActions) Setup() ([]byte, error) {
 			branch = g.defaultBranch
 		}
 
+		// check if the branch is protected
+		githubBranch, _, err := client.Repositories.GetBranch(
+			context.Background(),
+			g.GitRepoOwner,
+			g.GitRepoName,
+			branch,
+			true,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
 		isOAuth := g.GithubOAuthIntegration != nil
 
-		_, err = commitGithubFile(client, g.getPorterYMLFileName(), workflowYAML, g.GitRepoOwner, g.GitRepoName, branch, isOAuth)
+		if githubBranch.GetProtected() {
+			err = createBranchIfNotExists(client, g.GitRepoOwner, g.GitRepoName, branch, "porter-setup")
+
+			if err != nil {
+				return nil, fmt.Errorf(
+					"Unable to create PR to merge workflow files into protected branch: %s.\n"+
+						"To enable automatic deployments to Porter, please create a Github workflow "+
+						"file in this branch with the following contents:\n"+
+						"--------\n%s--------\nERROR: %w", branch, string(workflowYAML), ErrCreatePRForProtectedBranch,
+				)
+			}
+
+			_, err = commitWorkflowFile(client, g.getPorterYMLFileName(), workflowYAML, g.GitRepoOwner,
+				g.GitRepoName, "porter-setup", isOAuth)
+
+			if err != nil {
+				return nil, fmt.Errorf(
+					"Unable to create PR to merge workflow files into protected branch: %s.\n"+
+						"To enable automatic deployments to Porter, please create a Github workflow "+
+						"file in this branch with the following contents:\n"+
+						"--------\n%s--------\nERROR: %w", branch, string(workflowYAML), ErrCreatePRForProtectedBranch,
+				)
+			}
+
+			pr, _, err := client.PullRequests.Create(
+				context.Background(), g.GitRepoOwner, g.GitRepoName, &github.NewPullRequest{
+					Title: github.String("Enable Porter automatic deployments"),
+					Base:  github.String(branch),
+					Head:  github.String("porter-setup"),
+				},
+			)
+
+			if err != nil {
+				return nil, err
+			}
+
+			return nil, fmt.Errorf("Please merge %s to enable automatic deployments on Porter.\nERROR: %w",
+				pr.GetHTMLURL(), ErrProtectedBranch)
+		}
+
+		_, err = commitWorkflowFile(client, g.getPorterYMLFileName(), workflowYAML, g.GitRepoOwner,
+			g.GitRepoName, branch, isOAuth)
 		if err != nil {
 			return workflowYAML, err
 		}
@@ -402,7 +442,7 @@ func getPorterTokenSecretName(projectID uint) string {
 	return fmt.Sprintf("PORTER_TOKEN_%d", projectID)
 }
 
-func commitGithubFile(
+func commitWorkflowFile(
 	client *github.Client,
 	filename string,
 	contents []byte,

+ 88 - 14
internal/integrations/ci/actions/preview.go

@@ -79,7 +79,78 @@ func SetupEnv(opts *EnvOpts) error {
 		return err
 	}
 
-	_, err = commitGithubFile(
+	githubBranch, _, err := opts.Client.Repositories.GetBranch(
+		context.Background(), opts.GitRepoOwner, opts.GitRepoName, defaultBranch, true,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	if githubBranch.GetProtected() {
+		err = createBranchIfNotExists(opts.Client, opts.GitRepoOwner, opts.GitRepoName, defaultBranch, "porter-preview")
+
+		if err != nil {
+			return fmt.Errorf(
+				"Unable to create PR to merge workflow files into protected branch: %s.\n"+
+					"To enable Porter Preview Environment deployments, please create Github workflow "+
+					"files in this branch with the following contents:\n"+
+					"--------\n%s--------\n--------\n%s--------\nERROR: %w",
+				defaultBranch, string(applyWorkflowYAML), string(deleteWorkflowYAML), ErrCreatePRForProtectedBranch,
+			)
+		}
+
+		_, err = commitWorkflowFile(
+			opts.Client,
+			fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
+			applyWorkflowYAML, opts.GitRepoOwner,
+			opts.GitRepoName, "porter-preview", false,
+		)
+
+		if err != nil {
+			return fmt.Errorf(
+				"Unable to create PR to merge workflow files into protected branch: %s.\n"+
+					"To enable Porter Preview Environment deployments, please create Github workflow "+
+					"files in this branch with the following contents:\n"+
+					"--------\n%s--------\n--------\n%s--------\nERROR: %w",
+				defaultBranch, string(applyWorkflowYAML), string(deleteWorkflowYAML), ErrCreatePRForProtectedBranch,
+			)
+		}
+
+		_, err = commitWorkflowFile(
+			opts.Client,
+			fmt.Sprintf("porter_%s_delete_env.yml", strings.ToLower(opts.EnvironmentName)),
+			deleteWorkflowYAML, opts.GitRepoOwner,
+			opts.GitRepoName, "porter-preview", false,
+		)
+
+		if err != nil {
+			return fmt.Errorf(
+				"Unable to create PR to merge workflow files into protected branch: %s.\n"+
+					"To enable Porter Preview Environment deployments, please create a Github workflow "+
+					"file in this branch with the following contents:\n"+
+					"--------\n%s--------\nERROR: %w",
+				defaultBranch, string(deleteWorkflowYAML), ErrCreatePRForProtectedBranch,
+			)
+		}
+
+		pr, _, err := opts.Client.PullRequests.Create(
+			context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.NewPullRequest{
+				Title: github.String("Enable Porter Preview Environment deployments"),
+				Base:  github.String(defaultBranch),
+				Head:  github.String("porter-preview"),
+			},
+		)
+
+		if err != nil {
+			return err
+		}
+
+		return fmt.Errorf("Please merge %s to enable Porter Preview Environment deployments.\nERROR: %w",
+			pr.GetHTMLURL(), ErrProtectedBranch)
+	}
+
+	_, err = commitWorkflowFile(
 		opts.Client,
 		fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
 		applyWorkflowYAML,
@@ -92,13 +163,13 @@ func SetupEnv(opts *EnvOpts) error {
 	if err != nil {
 		if strings.Contains(err.Error(), "409 Could not create file") {
 			// possibly a write-protected branch
-			err = createPorterPreviewBranch(opts, defaultBranch)
+			err = createBranchIfNotExists(opts.Client, opts.GitRepoOwner, opts.GitRepoName, defaultBranch, "porter-preview")
 
 			if err != nil {
 				return fmt.Errorf("write-protected branch %s. Error creating porter-preview branch: %w", defaultBranch, err)
 			}
 
-			_, err = commitGithubFile(
+			_, err = commitWorkflowFile(
 				opts.Client,
 				fmt.Sprintf("porter_%s_env.yml", strings.ToLower(opts.EnvironmentName)),
 				applyWorkflowYAML,
@@ -112,7 +183,7 @@ func SetupEnv(opts *EnvOpts) error {
 				return fmt.Errorf("write-protected branch %s. Error committing to porter-preview branch: %w", defaultBranch, err)
 			}
 
-			_, err = commitGithubFile(
+			_, err = commitWorkflowFile(
 				opts.Client,
 				fmt.Sprintf("porter_%s_delete_env.yml", strings.ToLower(opts.EnvironmentName)),
 				deleteWorkflowYAML,
@@ -144,7 +215,7 @@ func SetupEnv(opts *EnvOpts) error {
 		return err
 	}
 
-	_, err = commitGithubFile(
+	_, err = commitWorkflowFile(
 		opts.Client,
 		fmt.Sprintf("porter_%s_delete_env.yml", strings.ToLower(opts.EnvironmentName)),
 		deleteWorkflowYAML,
@@ -310,25 +381,28 @@ func getPreviewDeleteActionYAML(opts *EnvOpts) ([]byte, error) {
 	return yaml.Marshal(actionYAML)
 }
 
-func createPorterPreviewBranch(opts *EnvOpts, defaultBranch string) error {
-	_, resp, err := opts.Client.Repositories.GetBranch(
-		context.Background(), opts.GitRepoOwner, opts.GitRepoName, "porter-preview", false,
+func createBranchIfNotExists(
+	client *github.Client,
+	gitRepoOwner, gitRepoName, baseBranch, headBranch string,
+) error {
+	_, resp, err := client.Repositories.GetBranch(
+		context.Background(), gitRepoOwner, gitRepoName, headBranch, true,
 	)
 
 	if resp.StatusCode == http.StatusNotFound {
-		branch, _, err := opts.Client.Repositories.GetBranch(
-			context.Background(), opts.GitRepoOwner, opts.GitRepoName, defaultBranch, false,
+		base, _, err := client.Repositories.GetBranch(
+			context.Background(), gitRepoOwner, gitRepoName, baseBranch, true,
 		)
 
 		if err != nil {
 			return err
 		}
 
-		_, _, err = opts.Client.Git.CreateRef(
-			context.Background(), opts.GitRepoOwner, opts.GitRepoName, &github.Reference{
-				Ref: github.String("refs/heads/porter-preview"),
+		_, _, err = client.Git.CreateRef(
+			context.Background(), gitRepoOwner, gitRepoName, &github.Reference{
+				Ref: github.String(fmt.Sprintf("refs/heads/%s", headBranch)),
 				Object: &github.GitObject{
-					SHA: branch.Commit.SHA,
+					SHA: base.Commit.SHA,
 				},
 			},
 		)