Преглед изворни кода

case for branch based deployments

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

+ 1 - 1
api/server/handlers/environment/create.go

@@ -105,7 +105,7 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 				"content_type": "json",
 				"secret":       c.Config().ServerConf.GithubIncomingWebhookSecret,
 			},
-			Events: []string{"pull_request"},
+			Events: []string{"pull_request", "push"},
 			Active: github.Bool(true),
 		},
 	)

+ 19 - 2
api/server/handlers/environment/enable_pull_request.go

@@ -69,8 +69,25 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 		if !found {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-				fmt.Errorf("base branch '%s' is not enabled for this preview environment, please enable it in the settings page",
-					request.BranchInto), http.StatusBadRequest,
+				fmt.Errorf("base branch '%s' is not enabled for this preview environment, please enable it "+
+					"in the settings page to continue", request.BranchInto), http.StatusBadRequest,
+			))
+			return
+		}
+	} else if len(envType.GitDeployBranches) > 0 {
+		found := false
+
+		for _, branch := range env.ToEnvironmentType().GitDeployBranches {
+			if branch == request.BranchFrom {
+				found = true
+				break
+			}
+		}
+
+		if found {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("head branch '%s' is enabled for branch deploys for this preview environment, "+
+					"please disable it in the settings page to continue", request.BranchInto), http.StatusBadRequest,
 			))
 			return
 		}

+ 32 - 30
api/server/handlers/environment/finalize_deployment.go

@@ -148,42 +148,44 @@ func (c *FinalizeDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		return
 	}
 
-	// add a check for the PR to be open before creating a comment
-	prClosed, err := isGithubPRClosed(client, owner, name, int(depl.PullRequestID))
+	if depl.PRBranchFrom != depl.PRBranchInto {
+		// add a check for the PR to be open before creating a comment
+		prClosed, err := isGithubPRClosed(client, owner, name, int(depl.PullRequestID))
 
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-			fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
-				depl.ID, err), http.StatusConflict,
-		))
-		return
-	}
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
+					depl.ID, err), http.StatusConflict,
+			))
+			return
+		}
 
-	if prClosed {
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("Github PR has been closed"),
-			http.StatusConflict))
-		return
-	}
+		if prClosed {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("Github PR has been closed"),
+				http.StatusConflict))
+			return
+		}
 
-	commentBody := "## Porter Preview Environments\n"
+		commentBody := "## Porter Preview Environments\n"
 
-	if depl.Subdomain == "" {
-		commentBody += fmt.Sprintf(
-			"✅ The latest SHA ([`%s`](https://github.com/%s/%s/commit/%s)) has been successfully deployed.",
-			depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA,
-		)
-	} else {
-		commentBody += fmt.Sprintf(
-			"✅ The latest SHA ([`%s`](https://github.com/%s/%s/commit/%s)) has been successfully deployed to %s",
-			depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA, depl.Subdomain,
-		)
-	}
+		if depl.Subdomain == "" {
+			commentBody += fmt.Sprintf(
+				"✅ The latest SHA ([`%s`](https://github.com/%s/%s/commit/%s)) has been successfully deployed.",
+				depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA,
+			)
+		} else {
+			commentBody += fmt.Sprintf(
+				"✅ The latest SHA ([`%s`](https://github.com/%s/%s/commit/%s)) has been successfully deployed to %s",
+				depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA, depl.Subdomain,
+			)
+		}
 
-	err = createOrUpdateComment(client, c.Repo(), env.NewCommentsDisabled, depl, github.String(commentBody))
+		err = createOrUpdateComment(client, c.Repo(), env.NewCommentsDisabled, depl, github.String(commentBody))
 
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
 	}
 
 	c.WriteResult(w, r, depl.ToDeploymentType())

+ 52 - 50
api/server/handlers/environment/finalize_deployment_with_errors.go

@@ -119,20 +119,6 @@ func (c *FinalizeDeploymentWithErrorsHandler) ServeHTTP(w http.ResponseWriter, r
 		return
 	}
 
-	// add a check for the PR to be open before creating a comment
-	prClosed, err := isGithubPRClosed(client, owner, name, int(depl.PullRequestID))
-
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
-		return
-	}
-
-	if prClosed {
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("github PR has been closed"),
-			http.StatusConflict))
-		return
-	}
-
 	depl.Status = types.DeploymentStatusFailed
 
 	// we do not care of the error in this case because the list deployments endpoint
@@ -147,51 +133,67 @@ func (c *FinalizeDeploymentWithErrorsHandler) ServeHTTP(w http.ResponseWriter, r
 		},
 	)
 
-	workflowRun, err := commonutils.GetLatestWorkflowRun(client, depl.RepoOwner, depl.RepoName,
-		fmt.Sprintf("porter_%s_env.yml", env.Name), depl.PRBranchFrom)
+	if depl.PRBranchFrom != depl.PRBranchInto {
+		// add a check for the PR to be open before creating a comment
+		prClosed, err := isGithubPRClosed(client, owner, name, int(depl.PullRequestID))
 
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
+			return
+		}
 
-	commentBody := fmt.Sprintf(
-		"## Porter Preview Environments\n"+
-			"❌ Errors encountered while deploying the changes\n"+
-			"||Deployment Information|\n"+
-			"|-|-|\n"+
-			"| Latest SHA | [`%s`](https://github.com/%s/%s/commit/%s) |\n"+
-			"| Build Logs | %s |\n",
-		depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA, workflowRun.GetHTMLURL(),
-	)
+		if prClosed {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("github PR has been closed"),
+				http.StatusConflict))
+			return
+		}
+
+		workflowRun, err := commonutils.GetLatestWorkflowRun(client, depl.RepoOwner, depl.RepoName,
+			fmt.Sprintf("porter_%s_env.yml", env.Name), depl.PRBranchFrom)
 
-	if len(request.SuccessfulResources) > 0 {
-		commentBody += "#### Successfully deployed resources\n"
-
-		for _, res := range request.SuccessfulResources {
-			if res.ReleaseType == "job" {
-				commentBody += fmt.Sprintf("- [`%s`](%s/jobs/%s/%s/%s?project_id=%d)\n",
-					res.ReleaseName, c.Config().ServerConf.ServerURL, cluster.Name, depl.Namespace,
-					res.ReleaseName, project.ID)
-			} else {
-				commentBody += fmt.Sprintf("- [`%s`](%s/applications/%s/%s/%s?project_id=%d)\n",
-					res.ReleaseName, c.Config().ServerConf.ServerURL, cluster.Name, depl.Namespace,
-					res.ReleaseName, project.ID)
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		commentBody := fmt.Sprintf(
+			"## Porter Preview Environments\n"+
+				"❌ Errors encountered while deploying the changes\n"+
+				"||Deployment Information|\n"+
+				"|-|-|\n"+
+				"| Latest SHA | [`%s`](https://github.com/%s/%s/commit/%s) |\n"+
+				"| Build Logs | %s |\n",
+			depl.CommitSHA, depl.RepoOwner, depl.RepoName, depl.CommitSHA, workflowRun.GetHTMLURL(),
+		)
+
+		if len(request.SuccessfulResources) > 0 {
+			commentBody += "#### Successfully deployed resources\n"
+
+			for _, res := range request.SuccessfulResources {
+				if res.ReleaseType == "job" {
+					commentBody += fmt.Sprintf("- [`%s`](%s/jobs/%s/%s/%s?project_id=%d)\n",
+						res.ReleaseName, c.Config().ServerConf.ServerURL, cluster.Name, depl.Namespace,
+						res.ReleaseName, project.ID)
+				} else {
+					commentBody += fmt.Sprintf("- [`%s`](%s/applications/%s/%s/%s?project_id=%d)\n",
+						res.ReleaseName, c.Config().ServerConf.ServerURL, cluster.Name, depl.Namespace,
+						res.ReleaseName, project.ID)
+				}
 			}
 		}
-	}
 
-	commentBody += "#### Failed resources\n"
+		commentBody += "#### Failed resources\n"
 
-	for res, err := range request.Errors {
-		commentBody += fmt.Sprintf("<details>\n  <summary><code>%s</code></summary>\n\n  **Error:** %s\n</details>\n", res, err)
-	}
+		for res, err := range request.Errors {
+			commentBody += fmt.Sprintf("<details>\n  <summary><code>%s</code></summary>\n\n  **Error:** %s\n</details>\n", res, err)
+		}
 
-	err = createOrUpdateComment(client, c.Repo(), env.NewCommentsDisabled, depl, github.String(commentBody))
+		err = createOrUpdateComment(client, c.Repo(), env.NewCommentsDisabled, depl, github.String(commentBody))
 
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
 	}
 
 	c.WriteResult(w, r, depl.ToDeploymentType())

+ 40 - 0
api/server/handlers/environment/update_environment_settings.go

@@ -1,6 +1,7 @@
 package environment
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"net/http"
@@ -92,6 +93,45 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 	changed = !reflect.DeepEqual(env.ToEnvironmentType().GitDeployBranches, newBranches)
 
 	if changed {
+		// let us check if the webhook has access to the "push" event
+		client, err := getGithubClientFromEnvironment(c.Config(), env)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		hook, _, err := client.Repositories.GetHook(
+			context.Background(), env.GitRepoOwner, env.GitRepoName, env.GithubWebhookID,
+		)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		found := false
+
+		for _, ev := range hook.Events {
+			if ev == "push" {
+				found = true
+				break
+			}
+		}
+
+		if !found {
+			hook.Events = append(hook.Events, "push")
+
+			_, _, err := client.Repositories.EditHook(
+				context.Background(), env.GitRepoOwner, env.GitRepoName, env.GithubWebhookID, hook,
+			)
+
+			if err != nil {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
+			}
+		}
+
 		env.GitDeployBranches = strings.Join(request.GitDeployBranches, ",")
 	}
 

+ 72 - 0
api/server/handlers/webhook/github_incoming.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"net/http"
 	"strconv"
+	"strings"
 	"sync"
 
 	"github.com/bradleyfalzon/ghinstallation/v2"
@@ -59,6 +60,13 @@ func (c *GithubIncomingWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error processing pull request webhook event: %w", err)))
 			return
 		}
+	case *github.PushEvent:
+		err = c.processPushEvent(event, r)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error processing push webhook event: %w", err)))
+			return
+		}
 	}
 }
 
@@ -103,6 +111,21 @@ func (c *GithubIncomingWebhookHandler) processPullRequestEvent(event *github.Pul
 		if !found {
 			return nil
 		}
+	} else if len(envType.GitDeployBranches) > 0 {
+		// if the pull request's head branch is in the list of deploy branches
+		// then we ignore it to avoid a double deploy
+		found := false
+
+		for _, br := range envType.GitDeployBranches {
+			if br == event.GetPullRequest().GetHead().GetRef() {
+				found = true
+				break
+			}
+		}
+
+		if found {
+			return nil
+		}
 	}
 
 	// create deployment on GitHub API
@@ -320,6 +343,55 @@ func (c *GithubIncomingWebhookHandler) deleteDeployment(
 	return nil
 }
 
+func (c *GithubIncomingWebhookHandler) processPushEvent(event *github.PushEvent, r *http.Request) error {
+	if !strings.HasPrefix(event.GetRef(), "refs/heads/") {
+		return nil
+	}
+
+	// get the webhook id from the request
+	webhookID, reqErr := requestutils.GetURLParamString(r, types.URLParamIncomingWebhookID)
+
+	if reqErr != nil {
+		return fmt.Errorf(reqErr.Error())
+	}
+
+	owner := event.GetRepo().GetOwner().GetLogin()
+	repo := event.GetRepo().GetName()
+
+	env, err := c.Repo().Environment().ReadEnvironmentByWebhookIDOwnerRepoName(webhookID, owner, repo)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil
+		}
+
+		return fmt.Errorf("[webhookID: %s, owner: %s, repo: %s] error reading environment: %w", webhookID, owner, repo, err)
+	}
+
+	envType := env.ToEnvironmentType()
+
+	branch := strings.TrimPrefix(event.GetRef(), "refs/heads/")
+
+	if len(envType.GitDeployBranches) > 0 {
+		found := false
+
+		for _, br := range envType.GitDeployBranches {
+			if br == branch {
+				found = true
+				break
+			}
+		}
+
+		if !found {
+			return nil
+		}
+	}
+
+	// FIXME: we should case on if env mode is auto or manual
+
+	return nil
+}
+
 func isSystemNamespace(namespace string) bool {
 	return namespace == "cert-manager" || namespace == "ingress-nginx" ||
 		namespace == "kube-node-lease" || namespace == "kube-public" ||