Ver Fonte

POR-1982 update cli apply to use update api (#3912)

ianedwards há 2 anos atrás
pai
commit
a7bce0bb95

+ 5 - 5
api/client/porter_app.go

@@ -480,17 +480,17 @@ func (c *Client) UpdateRevisionStatus(
 	return resp, err
 	return resp, err
 }
 }
 
 
-// GetBuildEnv returns the build environment for a given app proto
-func (c *Client) GetBuildEnv(
+// GetBuildFromRevision returns the build environment for a given app proto
+func (c *Client) GetBuildFromRevision(
 	ctx context.Context,
 	ctx context.Context,
 	projectID uint, clusterID uint,
 	projectID uint, clusterID uint,
 	appName string, appRevisionId string,
 	appName string, appRevisionId string,
-) (*porter_app.GetBuildEnvResponse, error) {
-	resp := &porter_app.GetBuildEnvResponse{}
+) (*porter_app.GetBuildFromRevisionResponse, error) {
+	resp := &porter_app.GetBuildFromRevisionResponse{}
 
 
 	err := c.getRequest(
 	err := c.getRequest(
 		fmt.Sprintf(
 		fmt.Sprintf(
-			"/projects/%d/clusters/%d/apps/%s/revisions/%s/build-env",
+			"/projects/%d/clusters/%d/apps/%s/revisions/%s/build",
 			projectID, clusterID, appName, appRevisionId,
 			projectID, clusterID, appName, appRevisionId,
 		),
 		),
 		nil,
 		nil,

+ 59 - 16
api/server/handlers/porter_app/get_build_env.go → api/server/handlers/porter_app/get_build.go

@@ -20,32 +20,50 @@ import (
 	"github.com/porter-dev/porter/internal/telemetry"
 	"github.com/porter-dev/porter/internal/telemetry"
 )
 )
 
 
-// GetBuildEnvHandler is the handler for the /apps/{porter_app_name}/revisions/{app_revision_id}/build-env endpoint
-type GetBuildEnvHandler struct {
+// GetBuildFromRevisionHandler is the handler for the /apps/{porter_app_name}/revisions/{app_revision_id}/build endpoint
+type GetBuildFromRevisionHandler struct {
 	handlers.PorterHandlerReadWriter
 	handlers.PorterHandlerReadWriter
 	authz.KubernetesAgentGetter
 	authz.KubernetesAgentGetter
 }
 }
 
 
-// NewGetBuildEnvHandler handles GET requests to the /apps/{porter_app_name}/revisions/{app_revision_id}/build-env endpoint
-func NewGetBuildEnvHandler(
+// NewGetBuildFromRevisionHandler handles GET requests to the /apps/{porter_app_name}/revisions/{app_revision_id}/build endpoint
+func NewGetBuildFromRevisionHandler(
 	config *config.Config,
 	config *config.Config,
 	decoderValidator shared.RequestDecoderValidator,
 	decoderValidator shared.RequestDecoderValidator,
 	writer shared.ResultWriter,
 	writer shared.ResultWriter,
-) *GetBuildEnvHandler {
-	return &GetBuildEnvHandler{
+) *GetBuildFromRevisionHandler {
+	return &GetBuildFromRevisionHandler{
 		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
 		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
 		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
 		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
 	}
 	}
 }
 }
 
 
-// GetBuildEnvResponse is the response object for the /apps/{porter_app_name}/revisions/{app_revision_id}/build-env endpoint
-type GetBuildEnvResponse struct {
+// Image is the image used by an app with a docker registry source
+type Image struct {
+	Repository string `json:"repository"`
+	Tag        string `json:"tag"`
+}
+
+// BuildSettings is the set of fields for a revision's build
+type BuildSettings struct {
+	Method     string   `json:"method"`
+	Context    string   `json:"context"`
+	Builder    string   `json:"builder"`
+	Buildpacks []string `json:"buildpacks"`
+	Dockerfile string   `json:"dockerfile"`
+	CommitSHA  string   `json:"commit_sha"`
+}
+
+// GetBuildFromRevisionResponse is the response object for the /apps/{porter_app_name}/revisions/{app_revision_id}/build endpoint
+type GetBuildFromRevisionResponse struct {
 	BuildEnvVariables map[string]string `json:"build_env_variables"`
 	BuildEnvVariables map[string]string `json:"build_env_variables"`
+	Build             BuildSettings     `json:"build"`
+	Image             Image             `json:"image"`
 }
 }
 
 
-// ServeHTTP translates the request into a GetBuildEnvRequest request, uses the proto to query the cluster for the build env, and returns the response
-func (c *GetBuildEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	ctx, span := telemetry.NewSpan(r.Context(), "serve-get-build-env")
+// ServeHTTP translates the request into a GetBuildFromRevisionRequest request, uses the proto to query the revision for the build settings, and returns the response
+func (c *GetBuildFromRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ctx, span := telemetry.NewSpan(r.Context(), "serve-get-build")
 	defer span.End()
 	defer span.End()
 
 
 	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
 	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
@@ -100,6 +118,8 @@ func (c *GetBuildEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
+	resp := &GetBuildFromRevisionResponse{}
+
 	appProto := &porterv1.PorterApp{}
 	appProto := &porterv1.PorterApp{}
 	err = helpers.UnmarshalContractObject(decoded, appProto)
 	err = helpers.UnmarshalContractObject(decoded, appProto)
 	if err != nil {
 	if err != nil {
@@ -108,6 +128,32 @@ func (c *GetBuildEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
+	if appProto.Build == nil {
+		err := telemetry.Error(ctx, span, nil, "app proto does not have build settings")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
+		return
+	}
+
+	resp.Build = BuildSettings{
+		Method:     appProto.Build.Method,
+		Context:    appProto.Build.Context,
+		Builder:    appProto.Build.Builder,
+		Buildpacks: appProto.Build.Buildpacks,
+		Dockerfile: appProto.Build.Dockerfile,
+		CommitSHA:  appProto.Build.CommitSha,
+	}
+
+	if appProto.Image == nil {
+		err := telemetry.Error(ctx, span, nil, "app proto does not have image settings. Tag is unknown")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
+		return
+	}
+
+	resp.Image = Image{
+		Repository: appProto.Image.Repository,
+		Tag:        appProto.Image.Tag,
+	}
+
 	agent, err := c.GetAgent(r, cluster, "")
 	agent, err := c.GetAgent(r, cluster, "")
 	if err != nil {
 	if err != nil {
 		err := telemetry.Error(ctx, span, err, "error getting agent")
 		err := telemetry.Error(ctx, span, err, "error getting agent")
@@ -147,10 +193,7 @@ func (c *GetBuildEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			buildEnvVariables[key] = val
 			buildEnvVariables[key] = val
 		}
 		}
 	}
 	}
+	resp.BuildEnvVariables = buildEnvVariables
 
 
-	res := &GetBuildEnvResponse{
-		BuildEnvVariables: buildEnvVariables,
-	}
-
-	c.WriteResult(w, r, res)
+	c.WriteResult(w, r, resp)
 }
 }

+ 6 - 6
api/server/router/porter_app.go

@@ -1212,14 +1212,14 @@ func getPorterAppRoutes(
 		Router:   r,
 		Router:   r,
 	})
 	})
 
 
-	// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/revisions/{app_revision_id}/build-env -> porter_app.NewGetBuildEnvHandler
-	getBuildEnvEndpoint := factory.NewAPIEndpoint(
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/revisions/{app_revision_id}/build-env -> porter_app.NewGetBuildFromRevisionHandler
+	getBuildFromRevisionEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
 		&types.APIRequestMetadata{
 			Verb:   types.APIVerbGet,
 			Verb:   types.APIVerbGet,
 			Method: types.HTTPVerbGet,
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
 			Path: &types.Path{
 				Parent:       basePath,
 				Parent:       basePath,
-				RelativePath: fmt.Sprintf("/apps/{%s}/revisions/{%s}/build-env", types.URLParamPorterAppName, types.URLParamAppRevisionID),
+				RelativePath: fmt.Sprintf("/apps/{%s}/revisions/{%s}/build", types.URLParamPorterAppName, types.URLParamAppRevisionID),
 			},
 			},
 			Scopes: []types.PermissionScope{
 			Scopes: []types.PermissionScope{
 				types.UserScope,
 				types.UserScope,
@@ -1229,15 +1229,15 @@ func getPorterAppRoutes(
 		},
 		},
 	)
 	)
 
 
-	getBuildEnvHandler := porter_app.NewGetBuildEnvHandler(
+	getBuildFromRevisionHandler := porter_app.NewGetBuildFromRevisionHandler(
 		config,
 		config,
 		factory.GetDecoderValidator(),
 		factory.GetDecoderValidator(),
 		factory.GetResultWriter(),
 		factory.GetResultWriter(),
 	)
 	)
 
 
 	routes = append(routes, &router.Route{
 	routes = append(routes, &router.Route{
-		Endpoint: getBuildEnvEndpoint,
-		Handler:  getBuildEnvHandler,
+		Endpoint: getBuildFromRevisionEndpoint,
+		Handler:  getBuildFromRevisionHandler,
 		Router:   r,
 		Router:   r,
 	})
 	})
 
 

+ 71 - 315
cli/cmd/v2/apply.go

@@ -12,14 +12,11 @@ import (
 
 
 	"github.com/porter-dev/porter/api/server/handlers/porter_app"
 	"github.com/porter-dev/porter/api/server/handlers/porter_app"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/kubernetes/environment_groups"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models"
 
 
 	"github.com/cli/cli/git"
 	"github.com/cli/cli/git"
 
 
 	"github.com/fatih/color"
 	"github.com/fatih/color"
-	"github.com/porter-dev/api-contracts/generated/go/helpers"
-	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
 	api "github.com/porter-dev/porter/api/client"
 	api "github.com/porter-dev/porter/api/client"
 	"github.com/porter-dev/porter/cli/cmd/config"
 	"github.com/porter-dev/porter/cli/cmd/config"
 )
 )
@@ -40,9 +37,6 @@ type ApplyInput struct {
 
 
 // Apply implements the functionality of the `porter apply` command for validate apply v2 projects
 // Apply implements the functionality of the `porter apply` command for validate apply v2 projects
 func Apply(ctx context.Context, inp ApplyInput) error {
 func Apply(ctx context.Context, inp ApplyInput) error {
-	const forceBuild = true
-	var b64AppProto string
-
 	cliConf := inp.CLIConfig
 	cliConf := inp.CLIConfig
 	client := inp.Client
 	client := inp.Client
 
 
@@ -75,116 +69,45 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 		}
 		}
 	}
 	}
 
 
-	// overrides incorporated into the app contract baed on the deployment target
-	var overrides *porter_app.EncodedAppWithEnv
-
-	// env variables and secrets to be passed to the apply endpoint
-	var envVariables map[string]string
-	var envSecrets map[string]string
-
-	appName := inp.AppName
+	var b64YAML string
 	if porterYamlExists {
 	if porterYamlExists {
 		porterYaml, err := os.ReadFile(filepath.Clean(inp.PorterYamlPath))
 		porterYaml, err := os.ReadFile(filepath.Clean(inp.PorterYamlPath))
 		if err != nil {
 		if err != nil {
 			return fmt.Errorf("could not read porter yaml file: %w", err)
 			return fmt.Errorf("could not read porter yaml file: %w", err)
 		}
 		}
 
 
-		b64YAML := base64.StdEncoding.EncodeToString(porterYaml)
-
-		// last argument is passed to accommodate users with v1 porter yamls
-		parseResp, err := client.ParseYAML(ctx, cliConf.Project, cliConf.Cluster, b64YAML, appName)
-		if err != nil {
-			return fmt.Errorf("error calling parse yaml endpoint: %w", err)
-		}
-
-		if parseResp.B64AppProto == "" {
-			return errors.New("b64 app proto is empty")
-		}
-		b64AppProto = parseResp.B64AppProto
-
-		overrides = parseResp.PreviewApp
-		envVariables = parseResp.EnvVariables
-		envSecrets = parseResp.EnvSecrets
-
-		// override app name if provided
-		appName, err = appNameFromB64AppProto(parseResp.B64AppProto)
-		if err != nil {
-			return fmt.Errorf("error getting app name from porter.yaml: %w", err)
-		}
-
-		// we only need to create the app if a porter yaml is provided (otherwise it must already exist)
-		createPorterAppDBEntryInp, err := createPorterAppDbEntryInputFromProtoAndEnv(parseResp.B64AppProto)
-		if err != nil {
-			return fmt.Errorf("unable to form porter app creation input from yaml: %w", err)
-		}
-
-		createPorterAppDBEntryInp.DeploymentTargetID = deploymentTargetID
-
-		err = client.CreatePorterAppDBEntry(ctx, cliConf.Project, cliConf.Cluster, createPorterAppDBEntryInp)
-		if err != nil {
-			if err.Error() == porter_app.ErrMissingSourceType.Error() {
-				return fmt.Errorf("cannot find existing Porter app with name %s and no build or image settings were specified in porter.yaml", appName)
-			}
-			return fmt.Errorf("unable to create porter app from yaml: %w", err)
-		}
-
-		color.New(color.FgGreen).Printf("Successfully parsed Porter YAML: applying app \"%s\"\n", appName) // nolint:errcheck,gosec
-	}
-
-	// b64AppOverrides is the base64-encoded app proto with preview environment specific overrides and env groups
-	var b64AppOverrides string
-
-	if inp.PreviewApply && overrides != nil {
-		b64AppOverrides = overrides.B64AppProto
-
-		previewEnvVariables := overrides.EnvVariables
-		envVariables = mergeEnvVariables(envVariables, previewEnvVariables)
-	}
-
-	if appName == "" {
-		return errors.New("App name is empty.  Please provide a Porter YAML file specifying the name of the app or set the PORTER_APP_NAME environment variable.")
+		b64YAML = base64.StdEncoding.EncodeToString(porterYaml)
+		color.New(color.FgGreen).Printf("Using Porter YAML at path: %s\n", inp.PorterYamlPath) // nolint:errcheck,gosec
 	}
 	}
 
 
 	commitSHA := commitSHAFromEnv()
 	commitSHA := commitSHAFromEnv()
+	gitSource, err := gitSourceFromEnv()
+	if err != nil {
+		return fmt.Errorf("error getting git source from env: %w", err)
+	}
 
 
-	validateResp, err := client.ValidatePorterApp(ctx, api.ValidatePorterAppInput{
+	updateInput := api.UpdateAppInput{
 		ProjectID:          cliConf.Project,
 		ProjectID:          cliConf.Project,
 		ClusterID:          cliConf.Cluster,
 		ClusterID:          cliConf.Cluster,
-		AppName:            appName,
-		Base64AppProto:     b64AppProto,
-		Base64AppOverrides: b64AppOverrides,
-		DeploymentTarget:   deploymentTargetID,
+		Name:               inp.AppName,
+		GitSource:          gitSource,
+		DeploymentTargetId: deploymentTargetID,
+		Base64PorterYAML:   b64YAML,
 		CommitSHA:          commitSHA,
 		CommitSHA:          commitSHA,
-	})
-	if err != nil {
-		return fmt.Errorf("error calling validate endpoint: %w", err)
 	}
 	}
 
 
-	if validateResp.ValidatedBase64AppProto == "" {
-		return errors.New("validated b64 app proto is empty")
-	}
-	base64AppProto := validateResp.ValidatedBase64AppProto
-
-	applyInput := api.ApplyPorterAppInput{
-		ProjectID:        cliConf.Project,
-		ClusterID:        cliConf.Cluster,
-		Base64AppProto:   base64AppProto,
-		DeploymentTarget: deploymentTargetID,
-		ForceBuild:       forceBuild,
-		Variables:        envVariables,
-		Secrets:          envSecrets,
-	}
-
-	applyResp, err := client.ApplyPorterApp(ctx, applyInput)
+	updateResp, err := client.UpdateApp(ctx, updateInput)
 	if err != nil {
 	if err != nil {
-		return fmt.Errorf("error calling apply endpoint: %w", err)
+		return fmt.Errorf("error calling update app endpoint: %w", err)
 	}
 	}
 
 
-	if applyResp.AppRevisionId == "" {
+	if updateResp.AppRevisionId == "" {
 		return errors.New("app revision id is empty")
 		return errors.New("app revision id is empty")
 	}
 	}
 
 
-	if applyResp.CLIAction == porterv1.EnumCLIAction_ENUM_CLI_ACTION_BUILD {
+	appName := updateResp.AppName
+
+	if updateResp.CLIAction == porter_app.CLIAction_Build {
 		color.New(color.FgGreen).Printf("Building new image...\n") // nolint:errcheck,gosec
 		color.New(color.FgGreen).Printf("Building new image...\n") // nolint:errcheck,gosec
 
 
 		eventID, _ := createBuildEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, commitSHA)
 		eventID, _ := createBuildEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, commitSHA)
@@ -194,71 +117,36 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 			appName:            appName,
 			appName:            appName,
 			cliConf:            cliConf,
 			cliConf:            cliConf,
 			deploymentTargetID: deploymentTargetID,
 			deploymentTargetID: deploymentTargetID,
-			appRevisionID:      applyResp.AppRevisionId,
+			appRevisionID:      updateResp.AppRevisionId,
 			eventID:            eventID,
 			eventID:            eventID,
 			commitSHA:          commitSHA,
 			commitSHA:          commitSHA,
 			prNumber:           prNumber,
 			prNumber:           prNumber,
 		}
 		}
 
 
 		if commitSHA == "" {
 		if commitSHA == "" {
-			err := errors.New("Build is required but commit SHA cannot be identified. Please set the PORTER_COMMIT_SHA environment variable or run apply in git repository with access to the git CLI.")
-			reportBuildFailureInput.buildError = err
-			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return err
-		}
-
-		buildSettings, err := buildSettingsFromBase64AppProto(base64AppProto)
-		if err != nil {
-			err := fmt.Errorf("error getting build settings from base64 app proto: %w", err)
-			reportBuildFailureInput.buildError = err
-			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return err
-		}
-
-		currentAppRevisionResp, err := client.CurrentAppRevision(ctx, cliConf.Project, cliConf.Cluster, appName, deploymentTargetID)
-		if err != nil {
-			err := fmt.Errorf("error getting current app revision: %w", err)
+			err := errors.New("build is required but commit SHA cannot be identified. Please set the PORTER_COMMIT_SHA environment variable or run apply in git repository with access to the git CLI")
 			reportBuildFailureInput.buildError = err
 			reportBuildFailureInput.buildError = err
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
 			return err
 			return err
 		}
 		}
 
 
-		if currentAppRevisionResp == nil {
-			err := errors.New("current app revision is nil")
-			reportBuildFailureInput.buildError = err
-			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return err
-		}
-
-		appRevision := currentAppRevisionResp.AppRevision
-		if appRevision.B64AppProto == "" {
-			err := errors.New("current app revision b64 app proto is empty")
-			reportBuildFailureInput.buildError = err
-			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return err
-		}
-
-		currentImageTag, err := imageTagFromBase64AppProto(appRevision.B64AppProto)
+		buildSettings, err := client.GetBuildFromRevision(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId)
 		if err != nil {
 		if err != nil {
-			err := fmt.Errorf("error getting image tag from current app revision: %w", err)
+			err := fmt.Errorf("error getting build from revision: %w", err)
 			reportBuildFailureInput.buildError = err
 			reportBuildFailureInput.buildError = err
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
 			return err
 			return err
 		}
 		}
 
 
-		buildSettings.CurrentImageTag = currentImageTag
-		buildSettings.ProjectID = cliConf.Project
-
-		buildEnv, err := client.GetBuildEnv(ctx, cliConf.Project, cliConf.Cluster, appName, appRevision.ID)
+		buildInput, err := buildInputFromBuildSettings(cliConf.Project, appName, buildSettings.Image, buildSettings.Build)
 		if err != nil {
 		if err != nil {
-			err := fmt.Errorf("error getting build env: %w", err)
+			err := fmt.Errorf("error creating build input from build settings: %w", err)
 			reportBuildFailureInput.buildError = err
 			reportBuildFailureInput.buildError = err
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
 			return err
 			return err
 		}
 		}
-		buildSettings.Env = buildEnv.BuildEnvVariables
 
 
-		buildOutput := build(ctx, client, buildSettings)
+		buildOutput := build(ctx, client, buildInput)
 		if buildOutput.Error != nil {
 		if buildOutput.Error != nil {
 			err := fmt.Errorf("error building app: %w", buildOutput.Error)
 			err := fmt.Errorf("error building app: %w", buildOutput.Error)
 			reportBuildFailureInput.buildLogs = buildOutput.Logs
 			reportBuildFailureInput.buildLogs = buildOutput.Logs
@@ -267,20 +155,22 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 			return err
 			return err
 		}
 		}
 
 
-		color.New(color.FgGreen).Printf("Successfully built image (tag: %s)\n", buildSettings.ImageTag) // nolint:errcheck,gosec
+		color.New(color.FgGreen).Printf("Successfully built image (tag: %s)\n", buildSettings.Image.Tag) // nolint:errcheck,gosec
 
 
 		buildMetadata := make(map[string]interface{})
 		buildMetadata := make(map[string]interface{})
 		buildMetadata["end_time"] = time.Now().UTC()
 		buildMetadata["end_time"] = time.Now().UTC()
 		_ = updateExistingEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, types.PorterAppEventType_Build, eventID, types.PorterAppEventStatus_Success, buildMetadata)
 		_ = updateExistingEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, types.PorterAppEventType_Build, eventID, types.PorterAppEventStatus_Success, buildMetadata)
 
 
-		applyInput = api.ApplyPorterAppInput{
-			ProjectID:     cliConf.Project,
-			ClusterID:     cliConf.Cluster,
-			AppRevisionID: applyResp.AppRevisionId,
-			ForceBuild:    !forceBuild,
+		updateInput = api.UpdateAppInput{
+			ProjectID:          cliConf.Project,
+			ClusterID:          cliConf.Cluster,
+			Name:               appName,
+			AppRevisionID:      updateResp.AppRevisionId,
+			Base64PorterYAML:   b64YAML,
+			DeploymentTargetId: deploymentTargetID,
 		}
 		}
 
 
-		applyResp, err = client.ApplyPorterApp(ctx, applyInput)
+		updateResp, err = client.UpdateApp(ctx, updateInput)
 		if err != nil {
 		if err != nil {
 			return fmt.Errorf("apply error post-build: %w", err)
 			return fmt.Errorf("apply error post-build: %w", err)
 		}
 		}
@@ -288,11 +178,11 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 
 
 	color.New(color.FgGreen).Printf("Image tag exists in repository\n") // nolint:errcheck,gosec
 	color.New(color.FgGreen).Printf("Image tag exists in repository\n") // nolint:errcheck,gosec
 
 
-	if applyResp.CLIAction == porterv1.EnumCLIAction_ENUM_CLI_ACTION_TRACK_PREDEPLOY {
+	if updateResp.CLIAction == porter_app.CLIAction_TrackPredeploy {
 		color.New(color.FgGreen).Printf("Waiting for predeploy to complete...\n") // nolint:errcheck,gosec
 		color.New(color.FgGreen).Printf("Waiting for predeploy to complete...\n") // nolint:errcheck,gosec
 
 
 		now := time.Now().UTC()
 		now := time.Now().UTC()
-		eventID, _ := createPredeployEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, now, applyResp.AppRevisionId, commitSHA)
+		eventID, _ := createPredeployEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, now, updateResp.AppRevisionId, commitSHA)
 		metadata := make(map[string]interface{})
 		metadata := make(map[string]interface{})
 		eventStatus := types.PorterAppEventStatus_Success
 		eventStatus := types.PorterAppEventStatus_Success
 		for {
 		for {
@@ -304,7 +194,7 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 				return errors.New("timed out waiting for predeploy to complete")
 				return errors.New("timed out waiting for predeploy to complete")
 			}
 			}
 
 
-			predeployStatusResp, err := client.PredeployStatus(ctx, cliConf.Project, cliConf.Cluster, appName, applyResp.AppRevisionId)
+			predeployStatusResp, err := client.PredeployStatus(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId)
 			if err != nil {
 			if err != nil {
 				eventStatus = types.PorterAppEventStatus_Failed
 				eventStatus = types.PorterAppEventStatus_Failed
 				metadata["end_time"] = time.Now().UTC()
 				metadata["end_time"] = time.Now().UTC()
@@ -327,33 +217,26 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 		metadata["end_time"] = time.Now().UTC()
 		metadata["end_time"] = time.Now().UTC()
 		_ = updateExistingEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, types.PorterAppEventType_PreDeploy, eventID, eventStatus, metadata)
 		_ = updateExistingEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, types.PorterAppEventType_PreDeploy, eventID, eventStatus, metadata)
 
 
-		applyInput = api.ApplyPorterAppInput{
-			ProjectID:     cliConf.Project,
-			ClusterID:     cliConf.Cluster,
-			AppRevisionID: applyResp.AppRevisionId,
-			ForceBuild:    !forceBuild,
-		}
-
-		applyResp, err = client.ApplyPorterApp(ctx, applyInput)
+		updateResp, err = client.UpdateApp(ctx, updateInput)
 		if err != nil {
 		if err != nil {
 			return fmt.Errorf("apply error post-predeploy: %w", err)
 			return fmt.Errorf("apply error post-predeploy: %w", err)
 		}
 		}
 	}
 	}
 
 
-	if applyResp.CLIAction != porterv1.EnumCLIAction_ENUM_CLI_ACTION_NONE {
-		return fmt.Errorf("unexpected CLI action: %s", applyResp.CLIAction)
+	if updateResp.CLIAction != porter_app.CLIAction_NoAction {
+		return fmt.Errorf("unexpected CLI action: %d", updateResp.CLIAction)
 	}
 	}
 
 
 	_, _ = client.ReportRevisionStatus(ctx, api.ReportRevisionStatusInput{
 	_, _ = client.ReportRevisionStatus(ctx, api.ReportRevisionStatusInput{
 		ProjectID:     cliConf.Project,
 		ProjectID:     cliConf.Project,
 		ClusterID:     cliConf.Cluster,
 		ClusterID:     cliConf.Cluster,
 		AppName:       appName,
 		AppName:       appName,
-		AppRevisionID: applyResp.AppRevisionId,
+		AppRevisionID: updateResp.AppRevisionId,
 		PRNumber:      prNumber,
 		PRNumber:      prNumber,
 		CommitSHA:     commitSHA,
 		CommitSHA:     commitSHA,
 	})
 	})
 
 
-	color.New(color.FgGreen).Printf("Successfully applied new revision %s for app %s\n", applyResp.AppRevisionId, appName) // nolint:errcheck,gosec
+	color.New(color.FgGreen).Printf("Successfully applied new revision %s for app %s\n", updateResp.AppRevisionId, appName) // nolint:errcheck,gosec
 	return nil
 	return nil
 }
 }
 
 
@@ -376,103 +259,48 @@ const checkPredeployTimeout = 60 * time.Minute
 // checkPredeployFrequency is the frequency at which the CLI will check the status of a predeploy
 // checkPredeployFrequency is the frequency at which the CLI will check the status of a predeploy
 const checkPredeployFrequency = 10 * time.Second
 const checkPredeployFrequency = 10 * time.Second
 
 
-func appNameFromB64AppProto(base64AppProto string) (string, error) {
-	decoded, err := base64.StdEncoding.DecodeString(base64AppProto)
-	if err != nil {
-		return "", fmt.Errorf("unable to decode base64 app for revision: %w", err)
-	}
+func gitSourceFromEnv() (porter_app.GitSource, error) {
+	var source porter_app.GitSource
 
 
-	app := &porterv1.PorterApp{}
-	err = helpers.UnmarshalContractObject(decoded, app)
-	if err != nil {
-		return "", fmt.Errorf("unable to unmarshal app for revision: %w", err)
-	}
-
-	if app.Name == "" {
-		return "", fmt.Errorf("app does not contain name")
-	}
-	return app.Name, nil
-}
-
-func createPorterAppDbEntryInputFromProtoAndEnv(base64AppProto string) (api.CreatePorterAppDBEntryInput, error) {
-	var input api.CreatePorterAppDBEntryInput
-
-	decoded, err := base64.StdEncoding.DecodeString(base64AppProto)
-	if err != nil {
-		return input, fmt.Errorf("unable to decode base64 app for revision: %w", err)
-	}
-
-	app := &porterv1.PorterApp{}
-	err = helpers.UnmarshalContractObject(decoded, app)
-	if err != nil {
-		return input, fmt.Errorf("unable to unmarshal app for revision: %w", err)
-	}
-
-	if app.Name == "" {
-		return input, fmt.Errorf("app does not contain name")
-	}
-	input.AppName = app.Name
-
-	if app.Build != nil {
-		if os.Getenv("GITHUB_REPOSITORY_ID") == "" {
-			input.Local = true
-			return input, nil
-		}
-		gitRepoId, err := strconv.Atoi(os.Getenv("GITHUB_REPOSITORY_ID"))
+	var repoID uint
+	if os.Getenv("GITHUB_REPOSITORY_ID") != "" {
+		id, err := strconv.Atoi(os.Getenv("GITHUB_REPOSITORY_ID"))
 		if err != nil {
 		if err != nil {
-			return input, fmt.Errorf("unable to parse GITHUB_REPOSITORY_ID to int: %w", err)
+			return source, fmt.Errorf("unable to parse GITHUB_REPOSITORY_ID to int: %w", err)
 		}
 		}
-		input.GitRepoID = uint(gitRepoId)
-		input.GitRepoName = os.Getenv("GITHUB_REPOSITORY")
-		input.GitBranch = os.Getenv("GITHUB_REF_NAME")
-		input.PorterYamlPath = "porter.yaml"
-		return input, nil
-	}
-
-	if app.Image != nil {
-		input.ImageRepository = app.Image.Repository
-		input.ImageTag = app.Image.Tag
-		return input, nil
+		repoID = uint(id)
 	}
 	}
 
 
-	return input, nil
+	return porter_app.GitSource{
+		GitBranch:   os.Getenv("GITHUB_REF_NAME"),
+		GitRepoID:   repoID,
+		GitRepoName: os.Getenv("GITHUB_REPOSITORY"),
+	}, nil
 }
 }
 
 
-func buildSettingsFromBase64AppProto(base64AppProto string) (buildInput, error) {
+func buildInputFromBuildSettings(projectID uint, appName string, image porter_app.Image, build porter_app.BuildSettings) (buildInput, error) {
 	var buildSettings buildInput
 	var buildSettings buildInput
 
 
-	decoded, err := base64.StdEncoding.DecodeString(base64AppProto)
-	if err != nil {
-		return buildSettings, fmt.Errorf("unable to decode base64 app for revision: %w", err)
-	}
-
-	app := &porterv1.PorterApp{}
-	err = helpers.UnmarshalContractObject(decoded, app)
-	if err != nil {
-		return buildSettings, fmt.Errorf("unable to unmarshal app for revision: %w", err)
-	}
-
-	if app.Name == "" {
-		return buildSettings, fmt.Errorf("app does not contain name")
+	if appName == "" {
+		return buildSettings, errors.New("app name is empty")
 	}
 	}
-
-	if app.Build == nil {
-		return buildSettings, fmt.Errorf("app does not contain build settings")
+	if image.Tag == "" {
+		return buildSettings, errors.New("image tag is empty")
 	}
 	}
-
-	if app.Image == nil {
-		return buildSettings, fmt.Errorf("app does not contain image settings")
+	if build.Method == "" {
+		return buildSettings, errors.New("build method is empty")
 	}
 	}
 
 
 	return buildInput{
 	return buildInput{
-		AppName:       app.Name,
-		BuildContext:  app.Build.Context,
-		Dockerfile:    app.Build.Dockerfile,
-		BuildMethod:   app.Build.Method,
-		Builder:       app.Build.Builder,
-		BuildPacks:    app.Build.Buildpacks,
-		ImageTag:      app.Image.Tag,
-		RepositoryURL: app.Image.Repository,
+		ProjectID:     projectID,
+		AppName:       appName,
+		BuildContext:  build.Context,
+		Dockerfile:    build.Dockerfile,
+		BuildMethod:   build.Method,
+		Builder:       build.Builder,
+		BuildPacks:    build.Buildpacks,
+		ImageTag:      image.Tag,
+		RepositoryURL: image.Repository,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -505,7 +333,7 @@ func deploymentTargetFromConfig(ctx context.Context, client api.Client, projectI
 		}
 		}
 
 
 		if branchName == "" {
 		if branchName == "" {
-			return deploymentTargetID, errors.New("Branch name is empty. Please run apply in a git repository with access to the git CLI.")
+			return deploymentTargetID, errors.New("branch name is empty. Please run apply in a git repository with access to the git CLI")
 		}
 		}
 
 
 		targetResp, err := client.CreateDeploymentTarget(ctx, projectID, clusterID, branchName, true)
 		targetResp, err := client.CreateDeploymentTarget(ctx, projectID, clusterID, branchName, true)
@@ -522,78 +350,6 @@ func deploymentTargetFromConfig(ctx context.Context, client api.Client, projectI
 	return deploymentTargetID, nil
 	return deploymentTargetID, nil
 }
 }
 
 
-func imageTagFromBase64AppProto(base64AppProto string) (string, error) {
-	var image string
-
-	decoded, err := base64.StdEncoding.DecodeString(base64AppProto)
-	if err != nil {
-		return image, fmt.Errorf("unable to decode base64 app for revision: %w", err)
-	}
-
-	app := &porterv1.PorterApp{}
-	err = helpers.UnmarshalContractObject(decoded, app)
-	if err != nil {
-		return image, fmt.Errorf("unable to unmarshal app for revision: %w", err)
-	}
-
-	if app.Image == nil {
-		return image, fmt.Errorf("app does not contain image settings")
-	}
-
-	if app.Image.Tag == "" {
-		return image, fmt.Errorf("app does not contain image tag")
-	}
-
-	return app.Image.Tag, nil
-}
-
-func mergeEnvVariables(currentEnv, previousEnv map[string]string) map[string]string {
-	env := make(map[string]string)
-
-	for k, v := range previousEnv {
-		env[k] = v
-	}
-
-	for k, v := range currentEnv {
-		env[k] = v
-	}
-
-	return env
-}
-
-func updateEnvGroupsInProto(ctx context.Context, base64AppProto string, envGroups []environment_groups.EnvironmentGroup) (string, error) {
-	var editedB64AppProto string
-
-	decoded, err := base64.StdEncoding.DecodeString(base64AppProto)
-	if err != nil {
-		return editedB64AppProto, fmt.Errorf("unable to decode base64 app for revision: %w", err)
-	}
-
-	app := &porterv1.PorterApp{}
-	err = helpers.UnmarshalContractObject(decoded, app)
-	if err != nil {
-		return editedB64AppProto, fmt.Errorf("unable to unmarshal app for revision: %w", err)
-	}
-
-	egs := make([]*porterv1.EnvGroup, 0)
-	for _, envGroup := range envGroups {
-		egs = append(egs, &porterv1.EnvGroup{
-			Name:    envGroup.Name,
-			Version: int64(envGroup.Version),
-		})
-	}
-	app.EnvGroups = egs
-
-	marshalled, err := helpers.MarshalContractObject(ctx, app)
-	if err != nil {
-		return editedB64AppProto, fmt.Errorf("unable to marshal app back to json: %w", err)
-	}
-
-	editedB64AppProto = base64.StdEncoding.EncodeToString(marshalled)
-
-	return editedB64AppProto, nil
-}
-
 type reportBuildFailureInput struct {
 type reportBuildFailureInput struct {
 	client             api.Client
 	client             api.Client
 	appName            string
 	appName            string