Просмотр исходного кода

Merge pull request #2656 from porter-dev/feat/autocreate-branch-previews

[POR-815] Branch deployments should be auto deployed
jusrhee 3 лет назад
Родитель
Сommit
fc62eae3c4

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

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"net/http"
 	"strings"
+	"sync"
 
 	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -19,6 +20,7 @@ import (
 	"github.com/porter-dev/porter/internal/integrations/ci/actions"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models/integrations"
+	"gorm.io/gorm"
 )
 
 type CreateEnvironmentHandler struct {
@@ -209,9 +211,127 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		}
 	}
 
-	c.WriteResult(w, r, env.ToEnvironmentType())
+	envType := env.ToEnvironmentType()
+
+	if len(envType.GitDeployBranches) > 0 && c.Config().ServerConf.EnableAutoPreviewBranchDeploy {
+		errs := autoDeployBranch(env, c.Config(), envType.GitDeployBranches, false)
+
+		if len(errs) > 0 {
+			errString := errs[0].Error()
+
+			for _, e := range errs {
+				errString += ": " + e.Error()
+			}
+
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("error auto deploying preview branches: %s", errString), http.StatusConflict),
+			)
+			return
+		}
+	}
+
+	c.WriteResult(w, r, envType)
 }
 
 func getGithubWebhookURLFromUID(serverURL, webhookUID string) string {
 	return fmt.Sprintf("%s/api/github/incoming_webhook/%s", serverURL, string(webhookUID))
 }
+
+func autoDeployBranch(
+	env *models.Environment,
+	config *config.Config,
+	branches []string,
+	onlyNewDeployments bool,
+) []error {
+	var (
+		errs []error
+		wg   sync.WaitGroup
+	)
+
+	for _, branch := range branches {
+		wg.Add(1)
+
+		go func(errs []error, branch string) {
+			defer wg.Done()
+			errs = append(errs, createWorkflowDispatchForBranch(env, config, onlyNewDeployments, branch)...)
+		}(errs, branch)
+	}
+
+	wg.Wait()
+
+	return errs
+}
+
+func createWorkflowDispatchForBranch(
+	env *models.Environment,
+	config *config.Config,
+	onlyNewDeployments bool,
+	branch string,
+) []error {
+	var errs []error
+
+	client, err := getGithubClientFromEnvironment(config, env)
+
+	if err != nil {
+		errs = append(errs, err)
+		return errs
+	}
+
+	var deplID uint
+
+	depl, err := config.Repo.Environment().ReadDeploymentForBranch(env.ID, env.GitRepoOwner, env.GitRepoName, branch)
+
+	if err == nil {
+		if onlyNewDeployments {
+			return errs
+		}
+
+		deplID = depl.ID
+	} else {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			depl, err := config.Repo.Environment().CreateDeployment(&models.Deployment{
+				EnvironmentID: env.ID,
+				Status:        types.DeploymentStatusCreating,
+				PRName:        fmt.Sprintf("Deployment for branch %s", branch),
+				RepoName:      env.GitRepoName,
+				RepoOwner:     env.GitRepoOwner,
+				PRBranchFrom:  branch,
+				PRBranchInto:  branch,
+			})
+
+			if err != nil {
+				errs = append(errs, fmt.Errorf("error creating deployment for branch %s: %w", branch, err))
+				return errs
+			}
+
+			deplID = depl.ID
+		} else {
+			errs = append(errs, fmt.Errorf("error reading deployment for branch %s: %w", branch, err))
+			return errs
+		}
+	}
+
+	if deplID == 0 {
+		errs = append(errs, fmt.Errorf("deployment id is 0 for branch %s", branch))
+		return errs
+	}
+
+	_, err = client.Actions.CreateWorkflowDispatchEventByFileName(
+		context.Background(), env.GitRepoOwner, env.GitRepoName, fmt.Sprintf("porter_%s_env.yml", env.Name),
+		github.CreateWorkflowDispatchEventRequest{
+			Ref: branch,
+			Inputs: map[string]interface{}{
+				"pr_number":      fmt.Sprintf("%d", deplID),
+				"pr_title":       fmt.Sprintf("Deployment for branch %s", branch),
+				"pr_branch_from": branch,
+				"pr_branch_into": branch,
+			},
+		},
+	)
+
+	if err != nil {
+		errs = append(errs, err)
+	}
+
+	return errs
+}

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

@@ -133,6 +133,23 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 		}
 
 		env.GitDeployBranches = strings.Join(request.GitDeployBranches, ",")
+
+		if len(request.GitDeployBranches) > 0 && c.Config().ServerConf.EnableAutoPreviewBranchDeploy {
+			errs := autoDeployBranch(env, c.Config(), request.GitDeployBranches, true)
+
+			if len(errs) > 0 {
+				errString := errs[0].Error()
+
+				for _, e := range errs {
+					errString += ": " + e.Error()
+				}
+
+				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+					fmt.Errorf("error auto deploying preview branches: %s", errString), http.StatusConflict),
+				)
+				return
+			}
+		}
 	}
 
 	if request.DisableNewComments != env.NewCommentsDisabled {

+ 4 - 0
api/server/shared/config/env/envconfs.go

@@ -111,6 +111,10 @@ type ServerConf struct {
 	// imagePullSecrets into a kubernetes deployment (Porter application)
 	DisablePullSecretsInjection bool `env:"DISABLE_PULL_SECRETS_INJECTION,default=false"`
 
+	// EnableAutoPreviewBranchDeploy is used to enable preview branch deployments automatically
+	// The default behaviour is to automatically create preview deployment against a deploy branch
+	EnableAutoPreviewBranchDeploy bool `env:"ENABLE_AUTO_PREVIEW_BRANCH_DEPLOY,default=true"`
+
 	// DisableTemporaryKubeconfig is used to denote if Porter should not
 	// create a temporary kubeconfig file for a cluster. When set to true, the
 	// /api/projects/{project_id}/clusters/{cluster_id}/kubeconfig will be disabled.