Ver código fonte

remove all forking based on beta feature enabled (#4259)

d-g-town 2 anos atrás
pai
commit
d957ae0e8b

+ 0 - 108
api/client/porter_app.go

@@ -197,93 +197,6 @@ func (c *Client) GetAppManifests(
 	return resp, err
 }
 
-// ValidatePorterAppInput is the input struct to ValidatePorterApp
-type ValidatePorterAppInput struct {
-	ProjectID          uint
-	ClusterID          uint
-	AppName            string
-	Base64AppProto     string
-	Base64AppOverrides string
-	DeploymentTarget   string
-	CommitSHA          string
-	ImageTagOverride   string
-}
-
-// ValidatePorterApp takes in a base64 encoded app definition that is potentially partial and returns a complete definition
-// using any previous app revisions and defaults
-func (c *Client) ValidatePorterApp(
-	ctx context.Context,
-	inp ValidatePorterAppInput,
-) (*porter_app.ValidatePorterAppResponse, error) {
-	resp := &porter_app.ValidatePorterAppResponse{}
-
-	req := &porter_app.ValidatePorterAppRequest{
-		AppName:            inp.AppName,
-		Base64AppProto:     inp.Base64AppProto,
-		Base64AppOverrides: inp.Base64AppOverrides,
-		DeploymentTargetId: inp.DeploymentTarget,
-		CommitSHA:          inp.CommitSHA,
-		ImageTagOverride:   inp.ImageTagOverride,
-	}
-
-	err := c.postRequest(
-		fmt.Sprintf(
-			"/projects/%d/clusters/%d/apps/validate",
-			inp.ProjectID, inp.ClusterID,
-		),
-		req,
-		resp,
-	)
-
-	return resp, err
-}
-
-// ApplyPorterAppInput is the input struct to ApplyPorterApp
-type ApplyPorterAppInput struct {
-	ProjectID        uint
-	ClusterID        uint
-	Base64AppProto   string
-	DeploymentTarget string
-	AppRevisionID    string
-	ForceBuild       bool
-	Variables        map[string]string
-	Secrets          map[string]string
-	HardEnvUpdate    bool
-}
-
-// ApplyPorterApp takes in a base64 encoded app definition and applies it to the cluster
-func (c *Client) ApplyPorterApp(
-	ctx context.Context,
-	inp ApplyPorterAppInput,
-) (*porter_app.ApplyPorterAppResponse, error) {
-	resp := &porter_app.ApplyPorterAppResponse{}
-
-	req := &porter_app.ApplyPorterAppRequest{
-		Base64AppProto:     inp.Base64AppProto,
-		DeploymentTargetId: inp.DeploymentTarget,
-		AppRevisionID:      inp.AppRevisionID,
-		ForceBuild:         inp.ForceBuild,
-		Variables:          inp.Variables,
-		Secrets:            inp.Secrets,
-		HardEnvUpdate:      inp.HardEnvUpdate,
-	}
-
-	err := c.postRequest(
-		fmt.Sprintf(
-			"/projects/%d/clusters/%d/apps/apply",
-			inp.ProjectID, inp.ClusterID,
-		),
-		req,
-		resp,
-		postRequestOpts{
-			retryCount:   3,
-			onlyRetry500: true,
-		},
-	)
-
-	return resp, err
-}
-
 // UpdateAppInput is the input struct to UpdateApp
 type UpdateAppInput struct {
 	ProjectID          uint
@@ -793,27 +706,6 @@ func (c *Client) RollbackRevision(
 	return resp, err
 }
 
-// UseNewApplyLogic checks whether the CLI should use the new apply logic
-func (c *Client) UseNewApplyLogic(
-	ctx context.Context,
-	projectID, clusterID uint,
-) (*porter_app.UseNewApplyLogicResponse, error) {
-	resp := &porter_app.UseNewApplyLogicResponse{}
-
-	req := &porter_app.UseNewApplyLogicRequest{}
-
-	err := c.getRequest(
-		fmt.Sprintf(
-			"/projects/%d/clusters/%d/apps/use-new-apply-logic",
-			projectID, clusterID,
-		),
-		req,
-		resp,
-	)
-
-	return resp, err
-}
-
 // RunAppJob runs a job for an app
 func (c *Client) RunAppJob(
 	ctx context.Context,

+ 0 - 268
api/server/handlers/porter_app/apply.go

@@ -1,268 +0,0 @@
-package porter_app
-
-import (
-	"context"
-	"encoding/base64"
-	"errors"
-	"fmt"
-	"net/http"
-
-	"connectrpc.com/connect"
-
-	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
-
-	"github.com/porter-dev/api-contracts/generated/go/helpers"
-
-	"github.com/porter-dev/porter/internal/deployment_target"
-	"github.com/porter-dev/porter/internal/porter_app"
-	"github.com/porter-dev/porter/internal/telemetry"
-
-	"github.com/porter-dev/porter/api/server/authz"
-	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/shared"
-	"github.com/porter-dev/porter/api/server/shared/apierrors"
-	"github.com/porter-dev/porter/api/server/shared/config"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// ApplyPorterAppHandler is the handler for the /apps/parse endpoint
-type ApplyPorterAppHandler struct {
-	handlers.PorterHandlerReadWriter
-	authz.KubernetesAgentGetter
-}
-
-// NewApplyPorterAppHandler handles POST requests to the endpoint /apps/apply
-func NewApplyPorterAppHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-	writer shared.ResultWriter,
-) *ApplyPorterAppHandler {
-	return &ApplyPorterAppHandler{
-		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
-		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
-	}
-}
-
-// ApplyPorterAppRequest is the request object for the /apps/apply endpoint
-type ApplyPorterAppRequest struct {
-	Base64AppProto     string            `json:"b64_app_proto"`
-	DeploymentTargetId string            `json:"deployment_target_id"`
-	AppRevisionID      string            `json:"app_revision_id"`
-	ForceBuild         bool              `json:"force_build"`
-	Variables          map[string]string `json:"variables"`
-	Secrets            map[string]string `json:"secrets"`
-	// HardEnvUpdate is used to remove any variables that are not specified in the request.  If false, the request will only update the variables specified in the request,
-	// and leave all other variables untouched.
-	HardEnvUpdate bool `json:"hard_env_update"`
-}
-
-// ApplyPorterAppResponse is the response object for the /apps/apply endpoint
-type ApplyPorterAppResponse struct {
-	AppRevisionId string                 `json:"app_revision_id"`
-	CLIAction     porterv1.EnumCLIAction `json:"cli_action"`
-}
-
-// ServeHTTP translates the request into a ApplyPorterApp request, forwards to the cluster control plane, and returns the response
-func (c *ApplyPorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	ctx, span := telemetry.NewSpan(r.Context(), "serve-apply-porter-app")
-	defer span.End()
-
-	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
-	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
-
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "project-id", Value: project.ID},
-		telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
-	)
-
-	if !project.GetFeatureFlag(models.ValidateApplyV2, c.Config().LaunchDarklyClient) {
-		err := telemetry.Error(ctx, span, nil, "project does not have validate apply v2 enabled")
-		c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
-		return
-	}
-
-	request := &ApplyPorterAppRequest{}
-	if ok := c.DecodeAndValidate(w, r, request); !ok {
-		err := telemetry.Error(ctx, span, nil, "error decoding request")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-		return
-	}
-
-	var appRevisionID string
-	var appProto *porterv1.PorterApp
-	var deploymentTargetID string
-
-	if request.AppRevisionID != "" {
-		appRevisionID = request.AppRevisionID
-		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-revision-id", Value: request.AppRevisionID})
-	} else {
-		if request.Base64AppProto == "" {
-			err := telemetry.Error(ctx, span, nil, "b64 yaml is empty")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-
-		decoded, err := base64.StdEncoding.DecodeString(request.Base64AppProto)
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error decoding base yaml")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-
-		appProto = &porterv1.PorterApp{}
-		err = helpers.UnmarshalContractObject(decoded, appProto)
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error unmarshalling app proto")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-
-		if request.DeploymentTargetId == "" {
-			err := telemetry.Error(ctx, span, nil, "deployment target id is empty")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-		deploymentTargetID = request.DeploymentTargetId
-
-		telemetry.WithAttributes(span,
-			telemetry.AttributeKV{Key: "app-name", Value: appProto.Name},
-			telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetId},
-		)
-
-		deploymentTargetDetails, err := deployment_target.DeploymentTargetDetails(ctx, deployment_target.DeploymentTargetDetailsInput{
-			ProjectID:          int64(project.ID),
-			ClusterID:          int64(cluster.ID),
-			DeploymentTargetID: deploymentTargetID,
-			CCPClient:          c.Config().ClusterControlPlaneClient,
-		})
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error getting deployment target details")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-			return
-		}
-
-		agent, err := c.GetAgent(r, cluster, "")
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error getting kubernetes agent")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-			return
-		}
-
-		subdomainCreateInput := porter_app.CreatePorterSubdomainInput{
-			AppName:             appProto.Name,
-			RootDomain:          c.Config().ServerConf.AppRootDomain,
-			DNSClient:           c.Config().DNSClient,
-			DNSRecordRepository: c.Repo().DNSRecord(),
-			KubernetesAgent:     agent,
-		}
-
-		appProto, err = addPorterSubdomainsIfNecessary(ctx, appProto, deploymentTargetDetails, subdomainCreateInput)
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error adding porter subdomains")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-	}
-
-	applyReq := connect.NewRequest(&porterv1.ApplyPorterAppRequest{
-		ProjectId:           int64(project.ID),
-		DeploymentTargetId:  deploymentTargetID,
-		App:                 appProto,
-		PorterAppRevisionId: appRevisionID,
-		ForceBuild:          request.ForceBuild,
-		AppEnv: &porterv1.EnvGroupVariables{
-			Normal: request.Variables,
-			Secret: request.Secrets,
-		},
-		IsHardEnvUpdate: request.HardEnvUpdate,
-	})
-	ccpResp, err := c.Config().ClusterControlPlaneClient.ApplyPorterApp(ctx, applyReq)
-	if err != nil {
-		err := telemetry.Error(ctx, span, err, "error calling ccp apply porter app")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	if ccpResp == nil {
-		err := telemetry.Error(ctx, span, err, "ccp resp is nil")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-	if ccpResp.Msg == nil {
-		err := telemetry.Error(ctx, span, err, "ccp resp msg is nil")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	if ccpResp.Msg.PorterAppRevisionId == "" {
-		err := telemetry.Error(ctx, span, err, "ccp resp app revision id is nil")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "resp-app-revision-id", Value: ccpResp.Msg.PorterAppRevisionId})
-
-	if ccpResp.Msg.CliAction == porterv1.EnumCLIAction_ENUM_CLI_ACTION_UNSPECIFIED {
-		err := telemetry.Error(ctx, span, err, "ccp resp cli action is nil")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "cli-action", Value: ccpResp.Msg.CliAction.String()})
-
-	response := &ApplyPorterAppResponse{
-		AppRevisionId: ccpResp.Msg.PorterAppRevisionId,
-		CLIAction:     ccpResp.Msg.CliAction,
-	}
-
-	c.WriteResult(w, r, response)
-}
-
-// addPorterSubdomainsIfNecessary adds porter subdomains to the app proto if a web service is changed to private and has no domains
-func addPorterSubdomainsIfNecessary(ctx context.Context, appProto *porterv1.PorterApp, deploymentTarget deployment_target.DeploymentTarget, createSubdomainInput porter_app.CreatePorterSubdomainInput) (*porterv1.PorterApp, error) {
-	ctx, span := telemetry.NewSpan(ctx, "add-porter-subdomains-if-necessary")
-	defer span.End()
-
-	// use deprecated services if service list is empty
-	if len(appProto.ServiceList) == 0 {
-		for _, service := range appProto.Services { // nolint:staticcheck
-			appProto.ServiceList = append(appProto.ServiceList, service)
-		}
-	}
-
-	for _, service := range appProto.ServiceList {
-		if service == nil {
-			continue
-		}
-		if service.Type == porterv1.ServiceType_SERVICE_TYPE_WEB {
-			webConfig := service.GetWebConfig()
-			if webConfig != nil && !webConfig.GetPrivate() && len(webConfig.Domains) == 0 {
-				if deploymentTarget.Namespace != DeploymentTargetSelector_Default {
-					createSubdomainInput.AppName = fmt.Sprintf("%s-%s", createSubdomainInput.AppName, deploymentTarget.ID[:6])
-				}
-
-				subdomain, err := porter_app.CreatePorterSubdomain(ctx, createSubdomainInput)
-				if err != nil {
-					return appProto, fmt.Errorf("error creating subdomain: %w", err)
-				}
-
-				if subdomain == "" {
-					return appProto, errors.New("response subdomain is empty")
-				}
-
-				webConfig.Domains = []*porterv1.Domain{
-					{Name: subdomain},
-				}
-			}
-		}
-	}
-
-	serviceMap := make(map[string]*porterv1.Service)
-	for _, service := range appProto.ServiceList {
-		serviceMap[service.Name] = service
-	}
-	appProto.Services = serviceMap // nolint:staticcheck
-
-	return appProto, nil
-}

+ 19 - 77
api/server/handlers/porter_app/attach_env_group.go

@@ -56,9 +56,6 @@ func (c *AttachEnvGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 
 	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "env-group-name", Value: request.EnvGroupName})
 
-	usingUpdateLogic := project.GetFeatureFlag(models.BetaFeaturesEnabled, c.Config().LaunchDarklyClient)
-	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "using-update-logic", Value: usingUpdateLogic})
-
 	if request.EnvGroupName == "" {
 		err := telemetry.Error(ctx, span, nil, "env group name cannot be empty")
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
@@ -74,84 +71,29 @@ func (c *AttachEnvGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 			return
 		}
 
-		// TODO: delete second branch once all projects are on update flow
-		if usingUpdateLogic {
-			updateReq := connect.NewRequest(&porterv1.UpdateAppRequest{
-				ProjectId: int64(project.ID),
-				DeploymentTargetIdentifier: &porterv1.DeploymentTargetIdentifier{
-					Id: appInstance.DeploymentTargetID.String(),
-				},
-				App: &porterv1.PorterApp{
-					Name: appInstance.Name,
-					EnvGroups: []*porterv1.EnvGroup{
-						{
-							Name: request.EnvGroupName,
-						},
-					},
-				},
-			})
-
-			_, err = c.Config().ClusterControlPlaneClient.UpdateApp(ctx, updateReq)
-			if err != nil {
-				telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: appInstanceId})
-				err := telemetry.Error(ctx, span, err, "error calling ccp update app")
-				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-				return
-			}
-		} else {
-			validateReq := connect.NewRequest(&porterv1.ValidatePorterAppRequest{
-				ProjectId:          int64(project.ID),
-				DeploymentTargetId: appInstance.DeploymentTargetID.String(),
-				App: &porterv1.PorterApp{
-					Name: appInstance.Name,
-					EnvGroups: []*porterv1.EnvGroup{
-						{
-							Name: request.EnvGroupName,
-						},
+		updateReq := connect.NewRequest(&porterv1.UpdateAppRequest{
+			ProjectId: int64(project.ID),
+			DeploymentTargetIdentifier: &porterv1.DeploymentTargetIdentifier{
+				Id: appInstance.DeploymentTargetID.String(),
+			},
+			App: &porterv1.PorterApp{
+				Name: appInstance.Name,
+				EnvGroups: []*porterv1.EnvGroup{
+					{
+						Name: request.EnvGroupName,
 					},
 				},
-			})
-			ccpResp, err := c.Config().ClusterControlPlaneClient.ValidatePorterApp(ctx, validateReq)
-			if err != nil {
-				telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: appInstanceId})
-				err := telemetry.Error(ctx, span, err, "error calling ccp validate porter app")
-				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-				return
-			}
-
-			if ccpResp == nil {
-				telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: appInstanceId})
-				err := telemetry.Error(ctx, span, err, "ccp resp is nil")
-				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-				return
-			}
-			if ccpResp.Msg == nil {
-				telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: appInstanceId})
-				err := telemetry.Error(ctx, span, err, "ccp resp msg is nil")
-				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-				return
-			}
+			},
+		})
 
-			if ccpResp.Msg.App == nil {
-				telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: appInstanceId})
-				err := telemetry.Error(ctx, span, err, "ccp resp app is nil")
-				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-				return
-			}
-
-			applyReq := connect.NewRequest(&porterv1.ApplyPorterAppRequest{
-				ProjectId:          int64(project.ID),
-				DeploymentTargetId: appInstance.DeploymentTargetID.String(),
-				App:                ccpResp.Msg.App,
-			})
-			_, err = c.Config().ClusterControlPlaneClient.ApplyPorterApp(ctx, applyReq)
-			if err != nil {
-				telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: appInstanceId})
-				err := telemetry.Error(ctx, span, err, "error calling ccp apply porter app")
-				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-				return
-			}
+		_, err = c.Config().ClusterControlPlaneClient.UpdateApp(ctx, updateReq)
+		if err != nil {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-instance-id", Value: appInstanceId})
+			err := telemetry.Error(ctx, span, err, "error calling ccp update app")
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+			return
 		}
+
 	}
 
 	c.WriteResult(w, r, nil)

+ 1 - 19
api/server/handlers/porter_app/create_and_update_events.go

@@ -193,25 +193,7 @@ func (p *CreateUpdatePorterAppEventHandler) createNewAppEvent(ctx context.Contex
 				}
 				return event, nil
 			} else {
-				betaFeaturesEnabled := project.GetFeatureFlag(models.BetaFeaturesEnabled, p.Config().LaunchDarklyClient)
-				telemetry.WithAttributes(span,
-					telemetry.AttributeKV{Key: "beta_features_enabled", Value: betaFeaturesEnabled},
-				)
-				// if beta features are not enabled, then porter makes a request to ccp to update the deploy status
-				// if beta features are enabled, ccp is checking the deploy status, so this request is not necessary
-				// TODO remove this entire branch once beta features are enabled by default
-				if !betaFeaturesEnabled {
-					err := p.updateDeployEventV2(ctx, updateDeployEventV2Input{
-						projectID:             cluster.ProjectID,
-						appName:               porterAppName,
-						appID:                 app.ID,
-						deploymentTargetID:    deploymentTargetID,
-						updatedStatusMetadata: requestMetadata,
-					})
-					if err != nil {
-						return types.PorterAppEvent{}, telemetry.Error(ctx, span, err, "error updating v2 deploy event")
-					}
-				}
+				// v2 handles its own deploy events
 				return types.PorterAppEvent{}, nil
 			}
 		}

+ 15 - 0
api/server/handlers/porter_app/update_app.go

@@ -37,6 +37,21 @@ func NewUpdateAppHandler(
 	}
 }
 
+// ServiceDeletions are deletions to apply to a specific service
+type ServiceDeletions struct {
+	DomainNames           []string `json:"domain_names"`
+	IngressAnnotationKeys []string `json:"ingress_annotation_keys"`
+}
+
+// Deletions are the names of services and env variables to delete
+type Deletions struct {
+	ServiceNames     []string                    `json:"service_names"`
+	Predeploy        []string                    `json:"predeploy"`
+	EnvVariableNames []string                    `json:"env_variable_names"`
+	EnvGroupNames    []string                    `json:"env_group_names"`
+	ServiceDeletions map[string]ServiceDeletions `json:"service_deletions"`
+}
+
 // UpdateAppRequest is the request object for the POST /apps/update endpoint
 type UpdateAppRequest struct {
 	// Name is the name of the app to update. If not specified, the name will be inferred from the porter yaml

+ 0 - 63
api/server/handlers/porter_app/use_new_apply_logic.go

@@ -1,63 +0,0 @@
-package porter_app
-
-import (
-	"net/http"
-
-	"github.com/porter-dev/porter/api/server/authz"
-	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/shared"
-	"github.com/porter-dev/porter/api/server/shared/config"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/models"
-	"github.com/porter-dev/porter/internal/telemetry"
-)
-
-// UseNewApplyLogicHandler returns whether the CLI should use the new apply logic or not
-type UseNewApplyLogicHandler struct {
-	handlers.PorterHandlerReadWriter
-	authz.KubernetesAgentGetter
-}
-
-// NewUseNewApplyLogicHandler returns a new UseNewApplyLogicHandler
-func NewUseNewApplyLogicHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-	writer shared.ResultWriter,
-) *UseNewApplyLogicHandler {
-	return &UseNewApplyLogicHandler{
-		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
-		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
-	}
-}
-
-// UseNewApplyLogicRequest is the request body for the /apps/use-new-apply-logic endpoint
-type UseNewApplyLogicRequest struct{}
-
-// UseNewApplyLogicResponse is the response body for the /apps/use-new-apply-logic endpoint
-type UseNewApplyLogicResponse struct {
-	UseNewApplyLogic bool `json:"use_new_apply_logic"`
-}
-
-// ServeHTTP handles the request on the /apps/use-new-apply-logic endpoint, allowing the server to tell the CLI whether to use the new apply logic or not
-func (c *UseNewApplyLogicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	ctx, span := telemetry.NewSpan(r.Context(), "serve-use-new-apply-logic")
-	defer span.End()
-
-	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
-	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
-
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "project_id", Value: project.ID},
-		telemetry.AttributeKV{Key: "cluster_id", Value: cluster.ID},
-	)
-
-	betaFeaturesEnabled := project.GetFeatureFlag(models.BetaFeaturesEnabled, c.Config().LaunchDarklyClient)
-
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "beta_features_enabled", Value: betaFeaturesEnabled},
-	)
-
-	c.WriteResult(w, r, &UseNewApplyLogicResponse{
-		UseNewApplyLogic: betaFeaturesEnabled,
-	})
-}

+ 0 - 224
api/server/handlers/porter_app/validate.go

@@ -1,224 +0,0 @@
-package porter_app
-
-import (
-	"encoding/base64"
-	"net/http"
-
-	"connectrpc.com/connect"
-
-	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
-
-	"github.com/porter-dev/api-contracts/generated/go/helpers"
-
-	"github.com/porter-dev/porter/internal/telemetry"
-
-	"github.com/porter-dev/porter/api/server/handlers"
-	"github.com/porter-dev/porter/api/server/shared"
-	"github.com/porter-dev/porter/api/server/shared/apierrors"
-	"github.com/porter-dev/porter/api/server/shared/config"
-	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/models"
-)
-
-// ValidatePorterAppHandler is handles requests to the /apps/validate endpoint
-type ValidatePorterAppHandler struct {
-	handlers.PorterHandlerReadWriter
-}
-
-// NewValidatePorterAppHandler returns a new ValidatePorterAppHandler
-func NewValidatePorterAppHandler(
-	config *config.Config,
-	decoderValidator shared.RequestDecoderValidator,
-	writer shared.ResultWriter,
-) *ValidatePorterAppHandler {
-	return &ValidatePorterAppHandler{
-		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
-	}
-}
-
-// ServiceDeletions are deletions to apply to a specific service
-type ServiceDeletions struct {
-	DomainNames           []string `json:"domain_names"`
-	IngressAnnotationKeys []string `json:"ingress_annotation_keys"`
-}
-
-// Deletions are the names of services and env variables to delete
-type Deletions struct {
-	ServiceNames     []string                    `json:"service_names"`
-	Predeploy        []string                    `json:"predeploy"`
-	EnvVariableNames []string                    `json:"env_variable_names"`
-	EnvGroupNames    []string                    `json:"env_group_names"`
-	ServiceDeletions map[string]ServiceDeletions `json:"service_deletions"`
-}
-
-// ValidatePorterAppRequest is the request object for the /apps/validate endpoint
-type ValidatePorterAppRequest struct {
-	AppName            string    `json:"app_name"`
-	Base64AppProto     string    `json:"b64_app_proto"`
-	Base64AppOverrides string    `json:"b64_app_overrides"`
-	DeploymentTargetId string    `json:"deployment_target_id"`
-	CommitSHA          string    `json:"commit_sha"`
-	ImageTagOverride   string    `json:"image_tag_override"`
-	Deletions          Deletions `json:"deletions"`
-}
-
-// ValidatePorterAppResponse is the response object for the /apps/validate endpoint
-type ValidatePorterAppResponse struct {
-	ValidatedBase64AppProto string `json:"validate_b64_app_proto"`
-}
-
-// ServeHTTP translates requests into protobuf objects and forwards them to the cluster control plane, returning the result
-func (c *ValidatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	ctx, span := telemetry.NewSpan(r.Context(), "serve-validate-porter-app")
-	defer span.End()
-
-	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
-	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
-
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "project-id", Value: project.ID},
-		telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
-	)
-
-	if !project.GetFeatureFlag(models.ValidateApplyV2, c.Config().LaunchDarklyClient) {
-		err := telemetry.Error(ctx, span, nil, "project does not have validate apply v2 enabled")
-		c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
-		return
-	}
-
-	request := &ValidatePorterAppRequest{}
-	if ok := c.DecodeAndValidate(w, r, request); !ok {
-		err := telemetry.Error(ctx, span, nil, "error decoding request")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-		return
-	}
-
-	appProto := &porterv1.PorterApp{}
-
-	if request.Base64AppProto == "" {
-		if request.AppName == "" {
-			err := telemetry.Error(ctx, span, nil, "app name is empty and no base64 proto provided")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-
-		appProto.Name = request.AppName
-	} else {
-		decoded, err := base64.StdEncoding.DecodeString(request.Base64AppProto)
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error decoding base yaml")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-
-		err = helpers.UnmarshalContractObject(decoded, appProto)
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error unmarshalling app proto")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-	}
-
-	if appProto.Name == "" {
-		err := telemetry.Error(ctx, span, nil, "app proto name is empty")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-		return
-	}
-
-	telemetry.WithAttributes(span,
-		telemetry.AttributeKV{Key: "app-name", Value: appProto.Name},
-		telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetId},
-		telemetry.AttributeKV{Key: "commit-sha", Value: request.CommitSHA},
-	)
-
-	var overrides *porterv1.PorterApp
-
-	if request.Base64AppOverrides != "" {
-		decoded, err := base64.StdEncoding.DecodeString(request.Base64AppOverrides)
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error decoding base  yaml")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-
-		overrides = &porterv1.PorterApp{}
-		err = helpers.UnmarshalContractObject(decoded, overrides)
-		if err != nil {
-			err := telemetry.Error(ctx, span, err, "error unmarshalling app proto")
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
-			return
-		}
-
-		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "validated-with-overrides", Value: true})
-	}
-
-	var serviceDeletions map[string]*porterv1.ServiceDeletions
-	if request.Deletions.ServiceDeletions != nil {
-		serviceDeletions = make(map[string]*porterv1.ServiceDeletions)
-		for k, v := range request.Deletions.ServiceDeletions {
-			serviceDeletions[k] = &porterv1.ServiceDeletions{
-				DomainNames:        v.DomainNames,
-				IngressAnnotations: v.IngressAnnotationKeys,
-			}
-		}
-	}
-
-	if request.ImageTagOverride != "" {
-		if appProto.Image == nil {
-			appProto.Image = &porterv1.AppImage{}
-		}
-		appProto.Image.Tag = request.ImageTagOverride
-	}
-
-	validateReq := connect.NewRequest(&porterv1.ValidatePorterAppRequest{
-		ProjectId:          int64(project.ID),
-		DeploymentTargetId: request.DeploymentTargetId,
-		CommitSha:          request.CommitSHA,
-		App:                appProto,
-		AppOverrides:       overrides,
-		Deletions: &porterv1.Deletions{
-			ServiceNames:     request.Deletions.ServiceNames,
-			PredeployNames:   request.Deletions.Predeploy,
-			EnvVariableNames: request.Deletions.EnvVariableNames,
-			EnvGroupNames:    request.Deletions.EnvGroupNames,
-			ServiceDeletions: serviceDeletions,
-		},
-	})
-	ccpResp, err := c.Config().ClusterControlPlaneClient.ValidatePorterApp(ctx, validateReq)
-	if err != nil {
-		err := telemetry.Error(ctx, span, err, "error calling ccp validate porter app")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	if ccpResp == nil {
-		err := telemetry.Error(ctx, span, err, "ccp resp is nil")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-	if ccpResp.Msg == nil {
-		err := telemetry.Error(ctx, span, err, "ccp resp msg is nil")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	if ccpResp.Msg.App == nil {
-		err := telemetry.Error(ctx, span, err, "ccp resp app is nil")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	encoded, err := helpers.MarshalContractObject(ctx, ccpResp.Msg.App)
-	if err != nil {
-		err := telemetry.Error(ctx, span, err, "error marshalling app proto back to json")
-		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
-		return
-	}
-
-	b64 := base64.StdEncoding.EncodeToString(encoded)
-	response := &ValidatePorterAppResponse{
-		ValidatedBase64AppProto: b64,
-	}
-
-	c.WriteResult(w, r, response)
-}

+ 0 - 87
api/server/router/porter_app.go

@@ -717,35 +717,6 @@ func getPorterAppRoutes(
 		Router:   r,
 	})
 
-	// POST /api/projects/{project_id}/clusters/{cluster_id}/apps/validate -> porter_app.NewValidatePorterAppHandler
-	validatePorterAppEndpoint := factory.NewAPIEndpoint(
-		&types.APIRequestMetadata{
-			Verb:   types.APIVerbGet,
-			Method: types.HTTPVerbPost,
-			Path: &types.Path{
-				Parent:       basePath,
-				RelativePath: fmt.Sprintf("%s/validate", relPathV2),
-			},
-			Scopes: []types.PermissionScope{
-				types.UserScope,
-				types.ProjectScope,
-				types.ClusterScope,
-			},
-		},
-	)
-
-	validatePorterAppHandler := porter_app.NewValidatePorterAppHandler(
-		config,
-		factory.GetDecoderValidator(),
-		factory.GetResultWriter(),
-	)
-
-	routes = append(routes, &router.Route{
-		Endpoint: validatePorterAppEndpoint,
-		Handler:  validatePorterAppHandler,
-		Router:   r,
-	})
-
 	// POST /api/projects/{project_id}/clusters/{cluster_id}/apps/create -> porter_app.NewCreateAppHandler
 	createAppEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
@@ -775,35 +746,6 @@ func getPorterAppRoutes(
 		Router:   r,
 	})
 
-	// POST /api/projects/{project_id}/clusters/{cluster_id}/apps/apply -> porter_app.NewApplyPorterAppHandler
-	applyPorterAppEndpoint := factory.NewAPIEndpoint(
-		&types.APIRequestMetadata{
-			Verb:   types.APIVerbUpdate,
-			Method: types.HTTPVerbPost,
-			Path: &types.Path{
-				Parent:       basePath,
-				RelativePath: fmt.Sprintf("%s/apply", relPathV2),
-			},
-			Scopes: []types.PermissionScope{
-				types.UserScope,
-				types.ProjectScope,
-				types.ClusterScope,
-			},
-		},
-	)
-
-	applyPorterAppHandler := porter_app.NewApplyPorterAppHandler(
-		config,
-		factory.GetDecoderValidator(),
-		factory.GetResultWriter(),
-	)
-
-	routes = append(routes, &router.Route{
-		Endpoint: applyPorterAppEndpoint,
-		Handler:  applyPorterAppHandler,
-		Router:   r,
-	})
-
 	// POST /api/projects/{project_id}/clusters/{cluster_id}/apps/attach-env-group -> porter_app.NewAttachEnvGroupHandler
 	attachEnvGroupEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
@@ -1676,35 +1618,6 @@ func getPorterAppRoutes(
 		Router:   r,
 	})
 
-	// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/use-new-apply-logic -> porter_app.NewUseNewApplyLogicHandler
-	useNewApplyLogicEndpoint := factory.NewAPIEndpoint(
-		&types.APIRequestMetadata{
-			Verb:   types.APIVerbGet,
-			Method: types.HTTPVerbGet,
-			Path: &types.Path{
-				Parent:       basePath,
-				RelativePath: fmt.Sprintf("%s/use-new-apply-logic", relPathV2),
-			},
-			Scopes: []types.PermissionScope{
-				types.UserScope,
-				types.ProjectScope,
-				types.ClusterScope,
-			},
-		},
-	)
-
-	useNewApplyLogicHandler := porter_app.NewUseNewApplyLogicHandler(
-		config,
-		factory.GetDecoderValidator(),
-		factory.GetResultWriter(),
-	)
-
-	routes = append(routes, &router.Route{
-		Endpoint: useNewApplyLogicEndpoint,
-		Handler:  useNewApplyLogicHandler,
-		Router:   r,
-	})
-
 	// POST /api/projects/{project_id}/clusters/{cluster_id}/apps/{app_name}/run -> porter_app.NewRunAppJobHandler
 	runAppJobEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 4 - 53
dashboard/src/lib/hooks/useAppValidation.ts

@@ -1,5 +1,5 @@
 import { useCallback, useContext } from "react";
-import { PorterApp } from "@porter-dev/api-contracts";
+import { type PorterApp } from "@porter-dev/api-contracts";
 import { match } from "ts-pattern";
 import { z } from "zod";
 
@@ -29,10 +29,7 @@ type ServiceDeletions = Record<
 >;
 
 type AppValidationHook = {
-  validateApp: (
-    data: PorterAppFormData,
-    skipValidation?: boolean
-  ) => Promise<AppValidationResult>;
+  validateApp: (data: PorterAppFormData) => Promise<AppValidationResult>;
   setServiceDeletions: (
     services: ClientPorterApp["services"]
   ) => ServiceDeletions;
@@ -109,10 +106,7 @@ export const useAppValidation = ({
   };
 
   const validateApp = useCallback(
-    async (
-      data: PorterAppFormData,
-      skipValidation = false
-    ): Promise<AppValidationResult> => {
+    async (data: PorterAppFormData): Promise<AppValidationResult> => {
       if (!currentProject || !currentCluster) {
         throw new Error("No project or cluster selected");
       }
@@ -158,50 +152,7 @@ export const useAppValidation = ({
         })
         .exhaustive();
 
-      if (skipValidation) {
-        return { validatedAppProto: proto, variables, secrets, commitSha };
-      }
-
-      const serviceDeletions = setServiceDeletions(data.app.services);
-
-      const res = await api.validatePorterApp(
-        "<token>",
-        {
-          b64_app_proto: btoa(
-            proto.toJsonString({
-              emitDefaultValues: true,
-            })
-          ),
-          deployment_target_id: deploymentTargetID,
-          commit_sha: commitSha,
-          deletions: {
-            service_names: data.deletions.serviceNames.map((s) => s.name),
-            predeploy: data.deletions.predeploy.map((s) => s.name),
-            env_group_names: data.deletions.envGroupNames.map((eg) => eg.name),
-            env_variable_names: [],
-            service_deletions: serviceDeletions,
-          },
-        },
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-        }
-      );
-
-      const validAppData = await z
-        .object({
-          validate_b64_app_proto: z.string(),
-        })
-        .parseAsync(res.data);
-
-      const validatedAppProto = PorterApp.fromJsonString(
-        atob(validAppData.validate_b64_app_proto),
-        {
-          ignoreUnknownFields: true,
-        }
-      );
-
-      return { validatedAppProto, variables, secrets, commitSha };
+      return { validatedAppProto: proto, variables, secrets, commitSha };
     },
     [deploymentTargetID, currentProject, currentCluster]
   );

+ 3 - 31
dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx

@@ -9,7 +9,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
 import { PorterApp } from "@porter-dev/api-contracts";
 import { useQueryClient } from "@tanstack/react-query";
 import axios from "axios";
-import _ from "lodash";
 import AnimateHeight from "react-animate-height";
 import { FormProvider, useForm } from "react-hook-form";
 import { useHistory } from "react-router";
@@ -218,10 +217,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
 
   const onSubmit = handleSubmit(async (data) => {
     try {
-      const { variables, secrets, validatedAppProto } = await validateApp(
-        data,
-        currentProject?.beta_features_enabled
-      );
+      const { variables, secrets, validatedAppProto } = await validateApp(data);
 
       const needsRebuild =
         buildIsDirty ||
@@ -233,7 +229,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
         return;
       }
 
-      if (currentProject?.beta_features_enabled && !buildIsDirty) {
+      if (!buildIsDirty) {
         const serviceDeletions = setServiceDeletions(data.app.services);
 
         const withPredeploy =
@@ -266,33 +262,9 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
         );
       }
 
-      // force_build will create a new 0 revision that will not be deployed
-      // but will be used to hydrate values when the workflow is run
-      if (!currentProject?.beta_features_enabled) {
-        await api.applyApp(
-          "<token>",
-          {
-            b64_app_proto: btoa(validatedAppProto.toJsonString()),
-            deployment_target_id: deploymentTarget.id,
-            force_build: needsRebuild,
-            variables,
-            secrets,
-            hard_env_update: true,
-          },
-          {
-            project_id: projectId,
-            cluster_id: clusterId,
-          }
-        );
-      }
-
       if (latestSource.type === "github" && needsRebuild) {
         // add a new revision with updated build settings only if they have changed
-        if (
-          currentProject?.beta_features_enabled &&
-          validatedAppProto.build &&
-          buildIsDirty
-        ) {
+        if (validatedAppProto.build && buildIsDirty) {
           await api.updateBuildSettings(
             "<token>",
             {

+ 38 - 90
dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx

@@ -225,7 +225,6 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
   }, [defaultDeploymentTarget]);
 
   const resetAllExceptName = (): void => {
-
     // Get the current name value before the reset
     setStep(0);
     const currentNameValue = porterAppFormMethods.getValues("app.name");
@@ -238,10 +237,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
   const onSubmit = handleSubmit(async (data) => {
     try {
       setDeployError("");
-      const { validatedAppProto, variables, secrets } = await validateApp(
-        data,
-        currentProject?.beta_features_enabled
-      );
+      const { validatedAppProto, variables, secrets } = await validateApp(data);
       setValidatedAppProto(validatedAppProto);
       setFinalizedAppEnv({ variables, secrets });
 
@@ -250,7 +246,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
         return;
       }
 
-      await createAndApply({
+      await update({
         app: validatedAppProto,
         source,
         variables,
@@ -267,51 +263,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
     }
   });
 
-  const createWithValidateApply = async ({
-    app,
-    projectID,
-    clusterID,
-    deploymentTargetID,
-    variables,
-    secrets,
-  }: {
-    app: PorterApp;
-    projectID: number;
-    clusterID: number;
-    deploymentTargetID: string;
-    variables: Record<string, string>;
-    secrets: Record<string, string>;
-  }): Promise<void> => {
-    await api.createApp(
-      "<token>",
-      {
-        ...source,
-        name: app.name,
-        deployment_target_id: deploymentTargetID,
-      },
-      {
-        project_id: projectID,
-        cluster_id: clusterID,
-      }
-    );
-
-    await api.applyApp(
-      "<token>",
-      {
-        b64_app_proto: btoa(app.toJsonString()),
-        deployment_target_id: deploymentTargetID,
-        variables,
-        secrets,
-        hard_env_update: true,
-      },
-      {
-        project_id: projectID,
-        cluster_id: clusterID,
-      }
-    );
-  };
-
-  const createAndApply = useCallback(
+  const update = useCallback(
     async ({
       app,
       source,
@@ -339,43 +291,32 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
           return false;
         }
 
-        if (currentProject.beta_features_enabled) {
-          await api.updateApp(
-            "<token>",
-            {
-              deployment_target_id: deploymentTargetID,
-              b64_app_proto: btoa(
-                app.toJsonString({
-                  emitDefaultValues: true,
-                })
-              ),
-              secrets,
-              variables,
-              is_env_override: true,
-              ...(source.type === "github" && {
-                git_source: {
-                  git_branch: source.git_branch,
-                  git_repo_id: source.git_repo_id,
-                  git_repo_name: source.git_repo_name,
-                },
-                porter_yaml_path: source.porter_yaml_path,
-              }),
-            },
-            {
-              project_id: currentProject.id,
-              cluster_id: currentCluster.id,
-            }
-          );
-        } else {
-          await createWithValidateApply({
-            app,
-            projectID: currentProject.id,
-            clusterID: currentCluster.id,
-            deploymentTargetID,
-            variables,
+        await api.updateApp(
+          "<token>",
+          {
+            deployment_target_id: deploymentTargetID,
+            b64_app_proto: btoa(
+              app.toJsonString({
+                emitDefaultValues: true,
+              })
+            ),
             secrets,
-          });
-        }
+            variables,
+            is_env_override: true,
+            ...(source.type === "github" && {
+              git_source: {
+                git_branch: source.git_branch,
+                git_repo_id: source.git_repo_id,
+                git_repo_name: source.git_repo_name,
+              },
+              porter_yaml_path: source.porter_yaml_path,
+            }),
+          },
+          {
+            project_id: currentProject.id,
+            cluster_id: currentCluster.id,
+          }
+        );
 
         // log analytics event that we successfully deployed
         void updateAppStep({
@@ -425,7 +366,6 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
       currentCluster?.id,
       deploymentTargetID,
       name.value,
-      createWithValidateApply,
     ]
   );
 
@@ -607,7 +547,15 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
                   <>
                     <Text size={16}>Application name</Text>
                     <Spacer y={0.5} />
-                    <Text color={isNameHighlight && porterAppFormMethods.getValues("app.name.value").length > 0 ? "#FFCC00" : "helper"}>
+                    <Text
+                      color={
+                        isNameHighlight &&
+                        porterAppFormMethods.getValues("app.name.value")
+                          .length > 0
+                          ? "#FFCC00"
+                          : "helper"
+                      }
+                    >
                       Lowercase letters, numbers, and &quot;-&quot; only.
                     </Text>
                     <Spacer y={0.5} />
@@ -842,7 +790,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
           projectId={currentProject.id}
           clusterId={currentCluster.id}
           deployPorterApp={async () =>
-            await createAndApply({
+            await update({
               app: validatedAppProto,
               source,
               variables: appEnv.variables,

+ 2 - 7
dashboard/src/main/home/cluster-dashboard/preview-environments/v2/setup-app/PreviewAppDataContainer.tsx

@@ -68,7 +68,6 @@ export const PreviewAppDataContainer: React.FC<Props> = ({
   existingTemplate,
 }) => {
   const history = useHistory();
-  const { currentProject } = useContext(Context);
 
   const [tab, setTab] = useState<PreviewEnvSettingsTab>("services");
   const [validatedAppProto, setValidatedAppProto] = useState<PorterApp | null>(
@@ -325,12 +324,8 @@ export const PreviewAppDataContainer: React.FC<Props> = ({
         options={[
           { label: "App Services", value: "services" },
           { label: "Environment Variables", value: "variables" },
-          ...(currentProject?.beta_features_enabled
-            ? [
-                { label: "Required Apps", value: "required-apps" },
-                { label: "Add-ons", value: "addons" },
-              ]
-            : []),
+          { label: "Required Apps", value: "required-apps" },
+          { label: "Add-ons", value: "addons" },
         ]}
         currentTab={tab}
         setCurrentTab={(tab: string) => {

+ 41 - 33
dashboard/src/main/home/sidebar/ProjectButton.tsx

@@ -1,16 +1,16 @@
-import React, { useState, useEffect, useRef, useContext } from "react";
+import React, { useContext, useEffect, useRef, useState } from "react";
+import { withRouter, type RouteComponentProps } from "react-router";
 import styled from "styled-components";
-import gradient from "assets/gradient.png";
+
+import Spacer from "components/porter/Spacer";
+import Tooltip from "components/porter/Tooltip";
 
 import { Context } from "shared/Context";
-import { ProjectListType, ProjectType } from "shared/types";
 import { pushFiltered } from "shared/routing";
-import { RouteComponentProps, withRouter } from "react-router";
-import Icon from "components/porter/Icon";
-import swap from "assets/swap.svg";
-import Spacer from "components/porter/Spacer";
+import { type ProjectListType, type ProjectType } from "shared/types";
+import gradient from "assets/gradient.png";
+
 import ProjectSelectionModal from "./ProjectSelectionModal";
-import Tooltip from "components/porter/Tooltip";
 
 type PropsType = RouteComponentProps & {
   currentProject: ProjectType;
@@ -34,11 +34,7 @@ const ProjectButton: React.FC<PropsType> = (props) => {
   }, []);
 
   const handleClickOutside = (e: any) => {
-    if (
-      wrapperRef &&
-      wrapperRef.current &&
-      !wrapperRef.current.contains(e.target)
-    ) {
+    if (wrapperRef?.current && !wrapperRef.current.contains(e.target)) {
       setExpanded(false);
     }
   };
@@ -48,7 +44,7 @@ const ProjectButton: React.FC<PropsType> = (props) => {
   };
 
   // Render the component
-  let { currentProject } = props;
+  const { currentProject } = props;
   if (currentProject) {
     return (
       <StyledProjectSection ref={wrapperRef}>
@@ -56,19 +52,27 @@ const ProjectButton: React.FC<PropsType> = (props) => {
           <ProjectSelectionModal
             currentProject={props.currentProject}
             projects={props.projects}
-            closeModal={() => setShowModal(false)}
+            closeModal={() => {
+              setShowModal(false);
+            }}
           />
         )}
 
-        {(user.isPorterUser && currentProject.simplified_view_enabled) ?
+        {user.isPorterUser && currentProject.simplified_view_enabled ? (
           <Tooltip
-            content={`Porter Apps ${currentProject.validate_apply_v2 ? currentProject.beta_features_enabled ? "V2 (Update)" : "V2 (Apply)" : "V1"}`}
+            content={`Porter Apps ${
+              currentProject.validate_apply_v2 ? "V2" : "V1"
+            }`}
             position="right"
           >
             <MainSelector
               projectsLength={props.projects.length}
               isPorterUser={user.isPorterUser}
-              onClick={() => (props.projects.length > 1 || user.isPorterUser) && setShowModal(true)} >
+              onClick={() => {
+                (props.projects.length > 1 || user.isPorterUser) &&
+                  setShowModal(true);
+              }}
+            >
               <ProjectIcon>
                 <ProjectImage src={gradient} />
                 <Letter>{currentProject.name[0].toUpperCase()}</Letter>
@@ -76,30 +80,34 @@ const ProjectButton: React.FC<PropsType> = (props) => {
               <ProjectName>{currentProject.name}</ProjectName>
             </MainSelector>
           </Tooltip>
-          :
+        ) : (
           <MainSelector
             projectsLength={props.projects.length}
             isPorterUser={user.isPorterUser}
-            onClick={() => (props.projects.length > 1 || user.isPorterUser) && setShowModal(true)} >
+            onClick={() => {
+              (props.projects.length > 1 || user.isPorterUser) &&
+                setShowModal(true);
+            }}
+          >
             <ProjectIcon>
               <ProjectImage src={gradient} />
               <Letter>{currentProject.name[0].toUpperCase()}</Letter>
             </ProjectIcon>
             <ProjectName>{currentProject.name}</ProjectName>
-            <Spacer inline x={.5} />
+            <Spacer inline x={0.5} />
           </MainSelector>
-        }
+        )}
         {/* {renderDropdown()} */}
-      </StyledProjectSection >
+      </StyledProjectSection>
     );
   }
   return (
     <InitializeButton
-      onClick={() =>
+      onClick={() => {
         pushFiltered(props, "/new-project", ["project_id"], {
           new_project: true,
-        })
-      }
+        });
+      }}
     >
       <Plus>+</Plus> Create a project
     </InitializeButton>
@@ -108,7 +116,6 @@ const ProjectButton: React.FC<PropsType> = (props) => {
 
 export default withRouter(ProjectButton);
 
-
 const ProjectLabel = styled.div`
   overflow: hidden;
   white-space: nowrap;
@@ -131,7 +138,7 @@ const InitializeButton = styled.div`
   font-size: 13px;
   font-weight: 500;
   border-radius: 3px;
-  color: ${props => props.theme.text.primary};
+  color: ${(props) => props.theme.text.primary};
   padding-bottom: 1px;
   cursor: pointer;
   background: #ffffff11;
@@ -146,7 +153,7 @@ const Option = styled.div`
   border-top: 1px solid #00000000;
   border-bottom: 1px solid
     ${(props: { selected: boolean; lastItem?: boolean }) =>
-    props.lastItem ? "#ffffff00" : "#ffffff15"};
+      props.lastItem ? "#ffffff00" : "#ffffff15"};
   height: 45px;
   display: flex;
   align-items: center;
@@ -159,7 +166,7 @@ const Option = styled.div`
     props.selected ? "#ffffff11" : ""};
   :hover {
     background: ${(props: { selected: boolean; lastItem?: boolean }) =>
-    props.selected ? "" : "#ffffff22"};
+      props.selected ? "" : "#ffffff22"};
   }
 
   > i {
@@ -221,7 +228,7 @@ const ProjectIconAlt = styled(ProjectIcon)`
 
 const StyledProjectSection = styled.div`
   position: relative;
-  color: ${props => props.theme.text.primary};
+  color: ${(props) => props.theme.text.primary};
   max-width: 200px;
 `;
 
@@ -231,7 +238,8 @@ const MainSelector = styled.div`
   justify-content: space-between;
   margin: 0;
   font-size: 14px;
-  cursor: ${props => (props.projectsLength > 1 || props.isPorterUser) ? "pointer" : "default"};
+  cursor: ${(props) =>
+    props.projectsLength > 1 || props.isPorterUser ? "pointer" : "default"};
   padding: 10px 22px;
   position: relative;
   :hover {
@@ -248,7 +256,7 @@ const MainSelector = styled.div`
     align-items: center;
     justify-content: center;
     border-radius: 20px;
-    background: "#ffffff22" 
+    background: "#ffffff22";
   }
 `;
 

+ 0 - 47
dashboard/src/shared/api.tsx

@@ -974,33 +974,6 @@ const getBranchHead = baseApi<
   }/${encodeURIComponent(pathParams.branch)}/head`;
 });
 
-const validatePorterApp = baseApi<
-  {
-    b64_app_proto: string;
-    deployment_target_id: string;
-    commit_sha: string;
-    deletions: {
-      service_names: string[];
-      predeploy: string[];
-      env_variable_names: string[];
-      env_group_names: string[];
-      service_deletions: Record<
-        string,
-        {
-          domain_names: string[];
-          ingress_annotation_keys: string[];
-        }
-      >;
-    };
-  },
-  {
-    project_id: number;
-    cluster_id: number;
-  }
->("POST", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/apps/validate`;
-});
-
 const createApp = baseApi<
   | {
       name: string;
@@ -1119,24 +1092,6 @@ const updateBuildSettings = baseApi<
   return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/apps/${pathParams.porter_app_name}/build`;
 });
 
-const applyApp = baseApi<
-  {
-    deployment_target_id: string;
-    b64_app_proto?: string;
-    app_revision_id?: string;
-    force_build?: boolean;
-    variables?: Record<string, string>;
-    secrets?: Record<string, string>;
-    hard_env_update?: boolean;
-  },
-  {
-    project_id: number;
-    cluster_id: number;
-  }
->("POST", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/apps/apply`;
-});
-
 const revertApp = baseApi<
   {
     deployment_target_id: string;
@@ -3593,13 +3548,11 @@ export default {
   getDefaultDeploymentTarget,
   deleteDeploymentTarget,
   getBranchHead,
-  validatePorterApp,
   createApp,
   createAppTemplate,
   updateApp,
   appRun,
   updateBuildSettings,
-  applyApp,
   revertApp,
   getAttachedEnvGroups,
   getLatestRevision,