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

Merge branch 'nafees/pe-features' into dev

Mohammed Nafees 3 лет назад
Родитель
Сommit
f873bfb3d3
28 измененных файлов с 482 добавлено и 17 удалено
  1. 3 0
      api/server/handlers/environment/common.go
  2. 8 1
      api/server/handlers/environment/create.go
  3. 8 0
      api/server/handlers/environment/create_deployment.go
  4. 8 0
      api/server/handlers/environment/delete.go
  5. 8 0
      api/server/handlers/environment/delete_deployment.go
  6. 9 0
      api/server/handlers/environment/enable_pull_request.go
  7. 8 0
      api/server/handlers/environment/finalize_deployment.go
  8. 8 0
      api/server/handlers/environment/finalize_deployment_with_errors.go
  9. 8 0
      api/server/handlers/environment/get_deployment.go
  10. 8 0
      api/server/handlers/environment/get_deployment_by_env.go
  11. 8 0
      api/server/handlers/environment/get_environment.go
  12. 8 0
      api/server/handlers/environment/list.go
  13. 8 0
      api/server/handlers/environment/list_deployments.go
  14. 8 0
      api/server/handlers/environment/list_deployments_by_cluster.go
  15. 8 0
      api/server/handlers/environment/reenable_deployment.go
  16. 8 0
      api/server/handlers/environment/toggle_new_comment.go
  17. 8 0
      api/server/handlers/environment/trigger_deployment_workflow.go
  18. 8 0
      api/server/handlers/environment/update_deployment.go
  19. 8 0
      api/server/handlers/environment/update_deployment_status.go
  20. 8 0
      api/server/handlers/environment/validate_porter_yaml.go
  21. 0 1
      api/types/environment.go
  22. 0 5
      cli/cmd/apply.go
  23. 42 0
      cmd/migrate/enable_cluster_preview_envs/enable.go
  24. 73 0
      cmd/migrate/enable_cluster_preview_envs/enable_test.go
  25. 182 0
      cmd/migrate/enable_cluster_preview_envs/helpers_test.go
  26. 28 0
      cmd/migrate/main.go
  27. 0 2
      internal/integrations/ci/actions/preview.go
  28. 1 8
      internal/integrations/ci/actions/steps.go

+ 3 - 0
api/server/handlers/environment/common.go

@@ -14,6 +14,9 @@ import (
 )
 
 var (
+	errPreviewProjectDisabled = errors.New("preview environments are not enabled for this project")
+	errPreviewClusterDisabled = errors.New("preview environments are not enabled for this cluster")
+
 	errDeploymentNotFound  = errors.New("no such deployment exists")
 	errEnvironmentNotFound = errors.New("no such environment exists")
 	errGithubAPI           = errors.New("error communicating with the github API")

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

@@ -41,6 +41,14 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
@@ -190,7 +198,6 @@ func (c *CreateEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		GitInstallationID: uint(ga.InstallationID),
 		EnvironmentName:   request.Name,
 		InstanceName:      c.Config().ServerConf.InstanceName,
-		CustomNamespace:   request.CustomNamespace,
 	})
 
 	if err != nil {

+ 8 - 0
api/server/handlers/environment/create_deployment.go

@@ -40,6 +40,14 @@ func (c *CreateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {

+ 8 - 0
api/server/handlers/environment/delete.go

@@ -41,6 +41,14 @@ func (c *DeleteEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {

+ 8 - 0
api/server/handlers/environment/delete_deployment.go

@@ -38,6 +38,14 @@ func (c *DeleteDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	deplID, reqErr := requestutils.GetURLParamUint(r, "deployment_id")
 
 	if reqErr != nil {

+ 9 - 0
api/server/handlers/environment/enable_pull_request.go

@@ -36,6 +36,15 @@ func NewEnablePullRequestHandler(
 func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	request := &types.PullRequest{}
 
 	if ok := c.DecodeAndValidate(w, r, request); !ok {

+ 8 - 0
api/server/handlers/environment/finalize_deployment.go

@@ -37,6 +37,14 @@ func (c *FinalizeDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {

+ 8 - 0
api/server/handlers/environment/finalize_deployment_with_errors.go

@@ -37,6 +37,14 @@ func (c *FinalizeDeploymentWithErrorsHandler) ServeHTTP(w http.ResponseWriter, r
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {

+ 8 - 0
api/server/handlers/environment/get_deployment.go

@@ -35,6 +35,14 @@ func (c *GetDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {

+ 8 - 0
api/server/handlers/environment/get_deployment_by_env.go

@@ -33,6 +33,14 @@ func (c *GetDeploymentByEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	envID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
 
 	if reqErr != nil {

+ 8 - 0
api/server/handlers/environment/get_environment.go

@@ -32,6 +32,14 @@ func (c *GetEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	envID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
 
 	if reqErr != nil {

+ 8 - 0
api/server/handlers/environment/list.go

@@ -29,6 +29,14 @@ func (c *ListEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	envs, err := c.Repo().Environment().ListEnvironments(project.ID, cluster.ID)
 
 	if err != nil {

+ 8 - 0
api/server/handlers/environment/list_deployments.go

@@ -35,6 +35,14 @@ func (c *ListDeploymentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	req := &types.ListDeploymentRequest{}
 
 	if ok := c.DecodeAndValidate(w, r, req); !ok {

+ 8 - 0
api/server/handlers/environment/list_deployments_by_cluster.go

@@ -34,6 +34,14 @@ func (c *ListDeploymentsByClusterHandler) ServeHTTP(w http.ResponseWriter, r *ht
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	req := &types.ListDeploymentRequest{}
 
 	if ok := c.DecodeAndValidate(w, r, req); !ok {

+ 8 - 0
api/server/handlers/environment/reenable_deployment.go

@@ -36,6 +36,14 @@ func (c *ReenableDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	deplID, reqErr := requestutils.GetURLParamUint(r, "deployment_id")
 
 	if reqErr != nil {

+ 8 - 0
api/server/handlers/environment/toggle_new_comment.go

@@ -36,6 +36,14 @@ func (c *ToggleNewCommentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	environmentID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
 
 	if reqErr != nil {

+ 8 - 0
api/server/handlers/environment/trigger_deployment_workflow.go

@@ -37,6 +37,14 @@ func (c *TriggerDeploymentWorkflowHandler) ServeHTTP(w http.ResponseWriter, r *h
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	deplID, reqErr := requestutils.GetURLParamUint(r, "deployment_id")
 
 	if reqErr != nil {

+ 8 - 0
api/server/handlers/environment/update_deployment.go

@@ -36,6 +36,14 @@ func (c *UpdateDeploymentHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {

+ 8 - 0
api/server/handlers/environment/update_deployment_status.go

@@ -36,6 +36,14 @@ func (c *UpdateDeploymentStatusHandler) ServeHTTP(w http.ResponseWriter, r *http
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {

+ 8 - 0
api/server/handlers/environment/validate_porter_yaml.go

@@ -40,6 +40,14 @@ func (c *ValidatePorterYAMLHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
+	if !project.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewProjectDisabled, http.StatusForbidden))
+		return
+	} else if !cluster.PreviewEnvsEnabled {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(errPreviewClusterDisabled, http.StatusForbidden))
+		return
+	}
+
 	envID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
 
 	if reqErr != nil {

+ 0 - 1
api/types/environment.go

@@ -24,7 +24,6 @@ type CreateEnvironmentRequest struct {
 	Mode                 string            `json:"mode" form:"oneof=auto manual" default:"manual"`
 	DisableNewComments   bool              `json:"disable_new_comments"`
 	GitRepoBranches      []string          `json:"git_repo_branches"`
-	CustomNamespace      bool              `json:"custom_namespaces"`
 	NamespaceAnnotations map[string]string `json:"namespace_annotations"`
 }
 

+ 0 - 5
cli/cmd/apply.go

@@ -757,11 +757,6 @@ func NewDeploymentHook(client *api.Client, resourceGroup *switchboardTypes.Resou
 func (t *DeploymentHook) PreApply() error {
 	if isSystemNamespace(t.namespace) {
 		color.New(color.FgYellow).Printf("attempting to deploy to system namespace '%s'\n", t.namespace)
-	} else if t.namespace == "SET_CUSTOM_NAMESPACE_HERE" {
-		// user wanted to use custom namespaces but forgot to update the workflow file
-		return fmt.Errorf("you need to replace 'SET_CUSTOM_NAMESPACE_HERE' with a custom namespace of your choice in "+
-			"the workflow file: https://github.com/%s/%s/blob/%s/.github/workflows/porter_preview_env.yml",
-			t.repoOwner, t.repoName, t.branchFrom)
 	}
 
 	envList, err := t.client.ListEnvironments(

+ 42 - 0
cmd/migrate/enable_cluster_preview_envs/enable.go

@@ -0,0 +1,42 @@
+package enable_cluster_preview_envs
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	lr "github.com/porter-dev/porter/pkg/logger"
+	_gorm "gorm.io/gorm"
+)
+
+func EnableClusterPreviewEnvs(db *_gorm.DB, logger *lr.Logger) error {
+	logger.Info().Msg("starting to enable preview envs for existing clusters whose parent projects have preview envs enabled")
+
+	var clusters []*models.Cluster
+
+	if err := db.Find(&clusters).Error; err != nil {
+		logger.Error().Msgf("failed to get clusters: %v", err)
+		return err
+	}
+
+	for _, c := range clusters {
+		project := &models.Project{}
+
+		if err := db.Model(project).Where("id = ?", c.ProjectID).First(project).Error; err != nil {
+			logger.Error().Msgf("failed to get project for cluster with ID %d: %v", c.ID, err)
+			return err
+		}
+
+		if project.PreviewEnvsEnabled {
+			c.PreviewEnvsEnabled = true
+
+			if err := db.Save(c).Error; err != nil {
+				logger.Error().Msgf("failed to update cluster with ID %d: %v", c.ID, err)
+				return err
+			}
+
+			logger.Info().Msgf("enabled preview envs for cluster with ID %d", c.ID)
+		}
+	}
+
+	logger.Info().Msg("cluster preview envs migration completed")
+
+	return nil
+}

+ 73 - 0
cmd/migrate/enable_cluster_preview_envs/enable_test.go

@@ -0,0 +1,73 @@
+package enable_cluster_preview_envs
+
+import (
+	"testing"
+
+	lr "github.com/porter-dev/porter/pkg/logger"
+)
+
+func TestEnableForProjectEnabled(t *testing.T) {
+	logger := lr.NewConsole(true)
+
+	tester := &tester{
+		dbFileName: "./porter_cluster_preview_envs_enabled.db",
+	}
+
+	setupTestEnv(tester, t)
+
+	defer cleanup(tester, t)
+
+	initProjectPreviewEnabled(tester, t)
+	initCluster(tester, t)
+
+	err := EnableClusterPreviewEnvs(tester.DB, logger)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+		return
+	}
+
+	cluster, err := tester.repo.Cluster().ReadCluster(1, 1)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+		return
+	}
+
+	if !cluster.PreviewEnvsEnabled {
+		t.Fatalf("expected preview envs to be enabled, got disabled")
+	}
+}
+
+func TestEnableForProjectDisabled(t *testing.T) {
+	logger := lr.NewConsole(true)
+
+	tester := &tester{
+		dbFileName: "./porter_cluster_preview_envs_disabled.db",
+	}
+
+	setupTestEnv(tester, t)
+
+	defer cleanup(tester, t)
+
+	initProjectPreviewDisabled(tester, t)
+	initCluster(tester, t)
+
+	err := EnableClusterPreviewEnvs(tester.DB, logger)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+		return
+	}
+
+	cluster, err := tester.repo.Cluster().ReadCluster(1, 1)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+		return
+	}
+
+	if cluster.PreviewEnvsEnabled {
+		t.Fatalf("expected preview envs to be disabled, got enabled")
+	}
+}

+ 182 - 0
cmd/migrate/enable_cluster_preview_envs/helpers_test.go

@@ -0,0 +1,182 @@
+package enable_cluster_preview_envs
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"github.com/porter-dev/porter/api/server/shared/config/env"
+	"github.com/porter-dev/porter/internal/adapter"
+	"github.com/porter-dev/porter/internal/models"
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+	"github.com/porter-dev/porter/internal/repository"
+	"github.com/porter-dev/porter/internal/repository/gorm"
+	_gorm "gorm.io/gorm"
+)
+
+type tester struct {
+	Key *[32]byte
+	DB  *_gorm.DB
+
+	repo       repository.Repository
+	dbFileName string
+	key        *[32]byte
+
+	initUsers    []*models.User
+	initProjects []*models.Project
+	initClusters []*models.Cluster
+	initKIs      []*ints.KubeIntegration
+}
+
+func setupTestEnv(tester *tester, t *testing.T) {
+	t.Helper()
+
+	db, err := adapter.New(&env.DBConf{
+		EncryptionKey: "__random_strong_encryption_key__",
+		SQLLite:       true,
+		SQLLitePath:   tester.dbFileName,
+	})
+
+	if err != nil {
+		t.Fatalf("%\n", err)
+	}
+
+	err = db.AutoMigrate(
+		&models.Project{},
+		&models.User{},
+		&models.Cluster{},
+		&ints.KubeIntegration{},
+		&ints.ClusterTokenCache{},
+	)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	var key [32]byte
+
+	for i, b := range []byte("__random_strong_encryption_key__") {
+		key[i] = b
+	}
+
+	tester.key = &key
+	tester.Key = &key
+	tester.DB = db
+
+	tester.repo = gorm.NewRepository(db, &key, nil)
+}
+
+func cleanup(tester *tester, t *testing.T) {
+	t.Helper()
+
+	// remove the created file file
+	os.Remove(tester.dbFileName)
+}
+
+func initUser(tester *tester, t *testing.T) {
+	t.Helper()
+
+	user := &models.User{
+		Email:    "example@example.com",
+		Password: "hello1234",
+	}
+
+	user, err := tester.repo.User().CreateUser(user)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	tester.initUsers = append(tester.initUsers, user)
+}
+
+func initCluster(tester *tester, t *testing.T) {
+	t.Helper()
+
+	if len(tester.initKIs) == 0 {
+		initKubeIntegration(tester, t)
+	}
+
+	cluster := &models.Cluster{
+		ProjectID:                tester.initProjects[0].ID,
+		Name:                     "cluster-test",
+		Server:                   "https://localhost",
+		KubeIntegrationID:        tester.initKIs[0].ID,
+		CertificateAuthorityData: []byte("-----BEGIN"),
+		TokenCache: ints.ClusterTokenCache{
+			TokenCache: ints.TokenCache{
+				Token:  []byte("token-1"),
+				Expiry: time.Now().Add(-1 * time.Hour),
+			},
+		},
+	}
+
+	cluster, err := tester.repo.Cluster().CreateCluster(cluster)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	tester.initClusters = append(tester.initClusters, cluster)
+}
+
+func initProjectPreviewEnabled(tester *tester, t *testing.T) {
+	t.Helper()
+
+	proj := &models.Project{
+		Name:               "project-test",
+		PreviewEnvsEnabled: true,
+	}
+
+	proj, err := tester.repo.Project().CreateProject(proj)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	tester.initProjects = append(tester.initProjects, proj)
+}
+
+func initProjectPreviewDisabled(tester *tester, t *testing.T) {
+	t.Helper()
+
+	proj := &models.Project{
+		Name: "project-test",
+	}
+
+	proj, err := tester.repo.Project().CreateProject(proj)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	tester.initProjects = append(tester.initProjects, proj)
+}
+
+func initKubeIntegration(tester *tester, t *testing.T) {
+	t.Helper()
+
+	if len(tester.initUsers) == 0 {
+		initUser(tester, t)
+	}
+
+	ki := &ints.KubeIntegration{
+		Mechanism:             ints.KubeLocal,
+		ProjectID:             tester.initProjects[0].ID,
+		UserID:                tester.initUsers[0].ID,
+		Kubeconfig:            []byte("current-context: testing\n"),
+		ClientCertificateData: []byte("clientcertdata"),
+		ClientKeyData:         []byte("clientkeydata"),
+		Token:                 []byte("token"),
+		Username:              []byte("username"),
+		Password:              []byte("password"),
+	}
+
+	ki, err := tester.repo.KubeIntegration().CreateKubeIntegration(ki)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	tester.initKIs = append(tester.initKIs, ki)
+}

+ 28 - 0
cmd/migrate/main.go

@@ -5,6 +5,7 @@ import (
 	"log"
 
 	"github.com/porter-dev/porter/api/server/shared/config/envloader"
+	"github.com/porter-dev/porter/cmd/migrate/enable_cluster_preview_envs"
 	"github.com/porter-dev/porter/cmd/migrate/keyrotate"
 	"github.com/porter-dev/porter/cmd/migrate/populate_source_config_display_name"
 	"github.com/porter-dev/porter/cmd/migrate/startup_migrations"
@@ -107,6 +108,14 @@ func main() {
 		}
 	}
 
+	if shouldEnableClusterPreviewEnvs() {
+		err := enable_cluster_preview_envs.EnableClusterPreviewEnvs(db, logger)
+
+		if err != nil {
+			logger.Fatal().Err(err).Msg("failed to enable cluster preview envs")
+		}
+	}
+
 	if err := InstanceMigrate(db, envConf.DBConf); err != nil {
 		logger.Fatal().Err(err).Msg("vault migration failed")
 	}
@@ -148,3 +157,22 @@ func shouldPopulateSourceConfigDisplayName() bool {
 
 	return c.PopulateSourceConfigDisplayName
 }
+
+type EnableClusterPreviewEnvsConf struct {
+	// we add a dummy field to avoid empty struct issue with envdecode
+	DummyField string `env:"ASDF,default=asdf"`
+
+	// if true, will mark all clusters to have preview envs enabled whose parent project has it enabled
+	EnableClusterPreviewEnvs bool `env:"ENABLE_CLUSTER_PREVIEW_ENVS"`
+}
+
+func shouldEnableClusterPreviewEnvs() bool {
+	var c EnableClusterPreviewEnvsConf
+
+	if err := envdecode.StrictDecode(&c); err != nil {
+		log.Fatalf("Failed to decode migration conf: %s", err)
+		return false
+	}
+
+	return c.EnableClusterPreviewEnvs
+}

+ 0 - 2
internal/integrations/ci/actions/preview.go

@@ -19,7 +19,6 @@ type EnvOpts struct {
 	EnvironmentName                         string
 	InstanceName                            string
 	ProjectID, ClusterID, GitInstallationID uint
-	CustomNamespace                         bool
 }
 
 func SetupEnv(opts *EnvOpts) error {
@@ -233,7 +232,6 @@ func getPreviewApplyActionYAML(opts *EnvOpts) ([]byte, error) {
 			opts.GitRepoOwner,
 			opts.GitRepoName,
 			"v0.2.1",
-			opts.CustomNamespace,
 		),
 	}
 

+ 1 - 8
internal/integrations/ci/actions/steps.go

@@ -44,9 +44,8 @@ func getCreatePreviewEnvStep(
 	serverURL, porterTokenSecretName string,
 	projectID, clusterID, gitInstallationID uint,
 	repoOwner, repoName, actionVersion string,
-	customNamespace bool,
 ) GithubActionYAMLStep {
-	step := GithubActionYAMLStep{
+	return GithubActionYAMLStep{
 		Name: "Create Porter preview env",
 		Uses: fmt.Sprintf("%s@%s", createPreviewActionName, actionVersion),
 		With: map[string]string{
@@ -67,10 +66,4 @@ func getCreatePreviewEnvStep(
 		},
 		Timeout: 30,
 	}
-
-	if customNamespace {
-		step.With["namespace"] = "SET_CUSTOM_NAMESPACE_HERE"
-	}
-
-	return step
 }