Feroze Mohideen 2 år sedan
förälder
incheckning
081d7038ac

+ 1 - 10
cli/cmd/commands/app.go

@@ -1258,7 +1258,7 @@ func appUpdateTag(ctx context.Context, user *types.GetAuthenticatedUserResponse,
 	}
 
 	if project.ValidateApplyV2 {
-		revisionId, err := v2.UpdateImage(ctx, v2.UpdateImageInput{
+		err := v2.UpdateImage(ctx, v2.UpdateImageInput{
 			ProjectID:            cliConf.Project,
 			ClusterID:            cliConf.Cluster,
 			AppName:              args[0],
@@ -1269,15 +1269,6 @@ func appUpdateTag(ctx context.Context, user *types.GetAuthenticatedUserResponse,
 		if err != nil {
 			return fmt.Errorf("error updating tag: %w", err)
 		}
-		if waitForSuccessfulUpdate {
-			return v2.WaitForAppRevisionStatus(ctx, v2.WaitForAppRevisionStatusInput{
-				ProjectID:  cliConf.Project,
-				ClusterID:  cliConf.Cluster,
-				AppName:    args[0],
-				RevisionID: revisionId,
-				Client:     client,
-			})
-		}
 		return nil
 	} else {
 		namespace := fmt.Sprintf("porter-stack-%s", args[0])

+ 1 - 10
cli/cmd/commands/apply.go

@@ -149,19 +149,10 @@ func apply(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client ap
 			AppName:        appName,
 			PreviewApply:   previewApply,
 		}
-		revisionId, err := v2.Apply(ctx, inp)
+		err := v2.Apply(ctx, inp)
 		if err != nil {
 			return err
 		}
-		if waitForSuccessfulUpdate {
-			return v2.WaitForAppRevisionStatus(ctx, v2.WaitForAppRevisionStatusInput{
-				ProjectID:  cliConfig.Project,
-				ClusterID:  cliConfig.Cluster,
-				AppName:    appName,
-				RevisionID: revisionId,
-				Client:     client,
-			})
-		}
 		return nil
 	}
 

+ 4 - 4
cli/cmd/v2/app_status.go

@@ -18,8 +18,8 @@ const (
 	DefaultRetryFrequencySeconds = 10
 )
 
-// WaitForAppRevisionStatusInput is the input for the WaitForAppRevisionStatus function
-type WaitForAppRevisionStatusInput struct {
+// waitForAppRevisionStatusInput is the input for the WaitForAppRevisionStatus function
+type waitForAppRevisionStatusInput struct {
 	ProjectID  uint
 	ClusterID  uint
 	AppName    string
@@ -27,8 +27,8 @@ type WaitForAppRevisionStatusInput struct {
 	Client     api.Client
 }
 
-// WaitForAppRevisionStatus waits for an app revision to complete
-func WaitForAppRevisionStatus(ctx context.Context, input WaitForAppRevisionStatusInput) error {
+// waitForAppRevisionStatus waits for an app revision to complete
+func waitForAppRevisionStatus(ctx context.Context, input waitForAppRevisionStatusInput) error {
 	timeoutMinutes := DefaultWaitTimeoutMinutes
 	timeout := time.Duration(timeoutMinutes) * time.Minute
 	deadline := time.Now().Add(timeout)

+ 43 - 31
cli/cmd/v2/apply.go

@@ -35,10 +35,12 @@ type ApplyInput struct {
 	AppName string
 	// PreviewApply is true when Apply should create a new deployment target matching current git branch and apply to that target
 	PreviewApply bool
+	// WaitForSuccessfulUpdate is true when Apply should wait for the update to complete before returning
+	WaitForSuccessfulUpdate bool
 }
 
 // Apply implements the functionality of the `porter apply` command for validate apply v2 projects
-func Apply(ctx context.Context, inp ApplyInput) (string, error) {
+func Apply(ctx context.Context, inp ApplyInput) error {
 	const forceBuild = true
 	var b64AppProto string
 
@@ -47,7 +49,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 
 	useNewApplyResp, err := client.UseNewApplyLogic(ctx, cliConf.Project, cliConf.Cluster)
 	if err != nil {
-		return "", fmt.Errorf("error checking if project uses new apply logic: %w", err)
+		return fmt.Errorf("error checking if project uses new apply logic: %w", err)
 	}
 
 	if useNewApplyResp.UseNewApplyLogic {
@@ -56,7 +58,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 
 	deploymentTargetID, err := deploymentTargetFromConfig(ctx, client, cliConf.Project, cliConf.Cluster, inp.PreviewApply)
 	if err != nil {
-		return "", fmt.Errorf("error getting deployment target from config: %w", err)
+		return fmt.Errorf("error getting deployment target from config: %w", err)
 	}
 
 	var prNumber int
@@ -64,7 +66,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 	if prNumberEnv != "" {
 		prNumber, err = strconv.Atoi(prNumberEnv)
 		if err != nil {
-			return "", fmt.Errorf("error parsing PORTER_PR_NUMBER to int: %w", err)
+			return fmt.Errorf("error parsing PORTER_PR_NUMBER to int: %w", err)
 		}
 	}
 
@@ -74,7 +76,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 		_, err := os.Stat(filepath.Clean(inp.PorterYamlPath))
 		if err != nil {
 			if !os.IsNotExist(err) {
-				return "", fmt.Errorf("error checking if porter yaml exists at path %s: %w", inp.PorterYamlPath, err)
+				return fmt.Errorf("error checking if porter yaml exists at path %s: %w", inp.PorterYamlPath, err)
 			}
 			// If a path was specified but the file does not exist, we will not immediately error out.
 			// This supports users migrated from v1 who use a workflow file that always specifies a porter yaml path
@@ -94,7 +96,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 	if porterYamlExists {
 		porterYaml, err := os.ReadFile(filepath.Clean(inp.PorterYamlPath))
 		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)
@@ -102,11 +104,11 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 		// 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)
+			return fmt.Errorf("error calling parse yaml endpoint: %w", err)
 		}
 
 		if parseResp.B64AppProto == "" {
-			return "", errors.New("b64 app proto is empty")
+			return errors.New("b64 app proto is empty")
 		}
 		b64AppProto = parseResp.B64AppProto
 
@@ -117,13 +119,13 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 		// 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)
+			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)
+			return fmt.Errorf("unable to form porter app creation input from yaml: %w", err)
 		}
 
 		createPorterAppDBEntryInp.DeploymentTargetID = deploymentTargetID
@@ -131,9 +133,9 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 		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("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)
+			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
@@ -150,7 +152,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 	}
 
 	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.")
+		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.")
 	}
 
 	commitSHA := commitSHAFromEnv()
@@ -165,11 +167,11 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 		CommitSHA:          commitSHA,
 	})
 	if err != nil {
-		return "", fmt.Errorf("error calling validate endpoint: %w", err)
+		return fmt.Errorf("error calling validate endpoint: %w", err)
 	}
 
 	if validateResp.ValidatedBase64AppProto == "" {
-		return "", errors.New("validated b64 app proto is empty")
+		return errors.New("validated b64 app proto is empty")
 	}
 	base64AppProto := validateResp.ValidatedBase64AppProto
 
@@ -185,11 +187,11 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 
 	applyResp, err := client.ApplyPorterApp(ctx, applyInput)
 	if err != nil {
-		return "", fmt.Errorf("error calling apply endpoint: %w", err)
+		return fmt.Errorf("error calling apply endpoint: %w", err)
 	}
 
 	if applyResp.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 {
@@ -212,7 +214,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 			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
+			return err
 		}
 
 		buildSettings, err := buildSettingsFromBase64AppProto(base64AppProto)
@@ -220,7 +222,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 			err := fmt.Errorf("error getting build settings from base64 app proto: %w", err)
 			reportBuildFailureInput.buildError = err
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return "", err
+			return err
 		}
 
 		currentAppRevisionResp, err := client.CurrentAppRevision(ctx, cliConf.Project, cliConf.Cluster, appName, deploymentTargetID)
@@ -228,14 +230,14 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 			err := fmt.Errorf("error getting current app revision: %w", err)
 			reportBuildFailureInput.buildError = err
 			_ = 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
+			return err
 		}
 
 		appRevision := currentAppRevisionResp.AppRevision
@@ -243,7 +245,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 			err := errors.New("current app revision b64 app proto is empty")
 			reportBuildFailureInput.buildError = err
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return "", err
+			return err
 		}
 
 		currentImageTag, err := imageTagFromBase64AppProto(appRevision.B64AppProto)
@@ -251,7 +253,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 			err := fmt.Errorf("error getting image tag from current app revision: %w", err)
 			reportBuildFailureInput.buildError = err
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return "", err
+			return err
 		}
 
 		buildSettings.CurrentImageTag = currentImageTag
@@ -262,7 +264,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 			err := fmt.Errorf("error getting build env: %w", err)
 			reportBuildFailureInput.buildError = err
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return "", err
+			return err
 		}
 		buildSettings.Env = buildEnv.BuildEnvVariables
 
@@ -272,7 +274,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 			reportBuildFailureInput.buildLogs = buildOutput.Logs
 			reportBuildFailureInput.buildError = buildOutput.Error
 			_ = reportBuildFailure(ctx, reportBuildFailureInput)
-			return "", err
+			return err
 		}
 
 		color.New(color.FgGreen).Printf("Successfully built image (tag: %s)\n", buildSettings.ImageTag) // nolint:errcheck,gosec
@@ -290,7 +292,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 
 		applyResp, err = client.ApplyPorterApp(ctx, applyInput)
 		if err != nil {
-			return "", fmt.Errorf("apply error post-build: %w", err)
+			return fmt.Errorf("apply error post-build: %w", err)
 		}
 	}
 
@@ -309,7 +311,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 				metadata["end_time"] = time.Now().UTC()
 				_ = updateExistingEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, types.PorterAppEventType_PreDeploy, eventID, eventStatus, metadata)
 
-				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)
@@ -318,7 +320,7 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 				metadata["end_time"] = time.Now().UTC()
 				_ = updateExistingEvent(ctx, client, appName, cliConf.Project, cliConf.Cluster, deploymentTargetID, types.PorterAppEventType_PreDeploy, eventID, eventStatus, metadata)
 
-				return "", fmt.Errorf("error calling predeploy status endpoint: %w", err)
+				return fmt.Errorf("error calling predeploy status endpoint: %w", err)
 			}
 
 			if predeployStatusResp.Status == porter_app.PredeployStatus_Failed {
@@ -344,12 +346,12 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 
 		applyResp, err = client.ApplyPorterApp(ctx, applyInput)
 		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)
+		return fmt.Errorf("unexpected CLI action: %s", applyResp.CLIAction)
 	}
 
 	_, _ = client.ReportRevisionStatus(ctx, api.ReportRevisionStatusInput{
@@ -362,7 +364,17 @@ func Apply(ctx context.Context, inp ApplyInput) (string, error) {
 	})
 
 	color.New(color.FgGreen).Printf("Successfully applied new revision %s for app %s\n", applyResp.AppRevisionId, appName) // nolint:errcheck,gosec
-	return applyResp.AppRevisionId, nil
+
+	if inp.WaitForSuccessfulUpdate {
+		return waitForAppRevisionStatus(ctx, waitForAppRevisionStatusInput{
+			ProjectID:  cliConf.Project,
+			ClusterID:  cliConf.Cluster,
+			AppName:    appName,
+			RevisionID: applyResp.AppRevisionId,
+			Client:     client,
+		})
+	}
+	return nil
 }
 
 func commitSHAFromEnv() string {

+ 1 - 1
cli/cmd/v2/deploy.go

@@ -20,7 +20,7 @@ func UpdateFull(ctx context.Context, cliConf config.CLIConfig, client api.Client
 		AppName:        appName,
 	}
 
-	_, err := Apply(ctx, inp)
+	err := Apply(ctx, inp)
 	if err != nil {
 		return err
 	}

+ 34 - 21
cli/cmd/v2/update.go

@@ -33,10 +33,12 @@ type UpdateInput struct {
 	AppName string
 	// PreviewApply is true when Update should create a new deployment target matching current git branch and apply to that target
 	PreviewApply bool
+	// WaitForSuccessfulUpdate is true when Update should wait for the revision deployment to complete (all services deployed successfully)
+	WaitForSuccessfulUpdate bool
 }
 
 // Update implements the functionality of the `porter apply` command for validate apply v2 projects
-func Update(ctx context.Context, inp UpdateInput) (string, error) {
+func Update(ctx context.Context, inp UpdateInput) error {
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 
@@ -56,7 +58,7 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 
 	deploymentTargetID, err := deploymentTargetFromConfig(ctx, client, cliConf.Project, cliConf.Cluster, inp.PreviewApply)
 	if err != nil {
-		return "", fmt.Errorf("error getting deployment target from config: %w", err)
+		return fmt.Errorf("error getting deployment target from config: %w", err)
 	}
 
 	var prNumber int
@@ -64,7 +66,7 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 	if prNumberEnv != "" {
 		prNumber, err = strconv.Atoi(prNumberEnv)
 		if err != nil {
-			return "", fmt.Errorf("error parsing PORTER_PR_NUMBER to int: %w", err)
+			return fmt.Errorf("error parsing PORTER_PR_NUMBER to int: %w", err)
 		}
 	}
 
@@ -74,7 +76,7 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 		_, err := os.Stat(filepath.Clean(inp.PorterYamlPath))
 		if err != nil {
 			if !os.IsNotExist(err) {
-				return "", fmt.Errorf("error checking if porter yaml exists at path %s: %w", inp.PorterYamlPath, err)
+				return fmt.Errorf("error checking if porter yaml exists at path %s: %w", inp.PorterYamlPath, err)
 			}
 			// If a path was specified but the file does not exist, we will not immediately error out.
 			// This supports users migrated from v1 who use a workflow file that always specifies a porter yaml path
@@ -87,7 +89,7 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 	if porterYamlExists {
 		porterYaml, err := os.ReadFile(filepath.Clean(inp.PorterYamlPath))
 		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)
@@ -97,7 +99,7 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 	commitSHA := commitSHAFromEnv()
 	gitSource, err := gitSourceFromEnv()
 	if err != nil {
-		return "", fmt.Errorf("error getting git source from env: %w", err)
+		return fmt.Errorf("error getting git source from env: %w", err)
 	}
 
 	updateInput := api.UpdateAppInput{
@@ -112,18 +114,18 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 
 	updateResp, err := client.UpdateApp(ctx, updateInput)
 	if err != nil {
-		return "", fmt.Errorf("error calling update app endpoint: %w", err)
+		return fmt.Errorf("error calling update app endpoint: %w", err)
 	}
 
 	if updateResp.AppRevisionId == "" {
-		return "", errors.New("app revision id is empty")
+		return errors.New("app revision id is empty")
 	}
 
 	appName := updateResp.AppName
 
 	buildSettings, err := client.GetBuildFromRevision(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId)
 	if err != nil {
-		return "", fmt.Errorf("error getting build from revision: %w", err)
+		return fmt.Errorf("error getting build from revision: %w", err)
 	}
 
 	if buildSettings != nil && buildSettings.Build.Method != "" {
@@ -159,7 +161,7 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 		}()
 
 		if commitSHA == "" {
-			return "", 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")
+			return 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")
 		}
 
 		color.New(color.FgGreen).Printf("Building new image with tag %s...\n", commitSHA) // nolint:errcheck,gosec
@@ -174,20 +176,20 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 		})
 		if err != nil {
 			buildError = fmt.Errorf("error creating build input from build settings: %w", err)
-			return "", buildError
+			return buildError
 		}
 
 		buildOutput := build(ctx, client, buildInput)
 		if buildOutput.Error != nil {
 			buildError = fmt.Errorf("error building app: %w", buildOutput.Error)
 			buildLogs = buildOutput.Logs
-			return "", buildError
+			return buildError
 		}
 
 		_, err = client.UpdateRevisionStatus(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId, models.AppRevisionStatus_BuildSuccessful)
 		if err != nil {
 			buildError = fmt.Errorf("error updating revision status post build: %w", err)
-			return "", buildError
+			return buildError
 		}
 
 		color.New(color.FgGreen).Printf("Successfully built image (tag: %s)\n", commitSHA) // nolint:errcheck,gosec
@@ -204,16 +206,16 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 
 	for {
 		if time.Since(now) > checkDeployTimeout {
-			return "", errors.New("timed out waiting for app to deploy")
+			return errors.New("timed out waiting for app to deploy")
 		}
 
 		status, err := client.GetRevisionStatus(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId)
 		if err != nil {
-			return "", fmt.Errorf("error getting app revision status: %w", err)
+			return fmt.Errorf("error getting app revision status: %w", err)
 		}
 
 		if status == nil {
-			return "", errors.New("unable to determine status of app revision")
+			return errors.New("unable to determine status of app revision")
 		}
 
 		if status.AppRevisionStatus.IsInTerminalStatus {
@@ -242,22 +244,33 @@ func Update(ctx context.Context, inp UpdateInput) (string, error) {
 
 	status, err := client.GetRevisionStatus(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId)
 	if err != nil {
-		return "", fmt.Errorf("error getting app revision status: %w", err)
+		return fmt.Errorf("error getting app revision status: %w", err)
 	}
 
 	if status == nil {
-		return "", errors.New("unable to determine status of app revision")
+		return errors.New("unable to determine status of app revision")
 	}
 
 	if status.AppRevisionStatus.InstallFailed {
-		return "", errors.New("app failed to deploy")
+		return errors.New("app failed to deploy")
 	}
 	if status.AppRevisionStatus.PredeployFailed {
-		return "", errors.New("predeploy failed for new revision")
+		return errors.New("predeploy failed for new revision")
 	}
 
 	color.New(color.FgGreen).Printf("Successfully applied new revision %s\n", updateResp.AppRevisionId) // nolint:errcheck,gosec
-	return updateResp.AppRevisionId, nil
+
+	if inp.WaitForSuccessfulUpdate {
+		return waitForAppRevisionStatus(ctx, waitForAppRevisionStatusInput{
+			ProjectID:  cliConf.Project,
+			ClusterID:  cliConf.Cluster,
+			AppName:    appName,
+			RevisionID: updateResp.AppRevisionId,
+			Client:     client,
+		})
+	}
+
+	return nil
 }
 
 // checkDeployTimeout is the timeout for checking if an app has been deployed

+ 21 - 10
cli/cmd/v2/update_image.go

@@ -12,18 +12,19 @@ import (
 
 // UpdateImageInput is the input for the UpdateImage function
 type UpdateImageInput struct {
-	ProjectID            uint
-	ClusterID            uint
-	AppName              string
-	DeploymentTargetName string
-	Tag                  string
-	Client               api.Client
+	ProjectID               uint
+	ClusterID               uint
+	AppName                 string
+	DeploymentTargetName    string
+	Tag                     string
+	Client                  api.Client
+	WaitForSuccessfulUpdate bool
 }
 
 // UpdateImage updates the image of an application
-func UpdateImage(ctx context.Context, input UpdateImageInput) (string, error) {
+func UpdateImage(ctx context.Context, input UpdateImageInput) error {
 	if input.DeploymentTargetName == "" {
-		return "", errors.New("please provide a deployment target")
+		return errors.New("please provide a deployment target")
 	}
 
 	tag := input.Tag
@@ -33,11 +34,21 @@ func UpdateImage(ctx context.Context, input UpdateImageInput) (string, error) {
 
 	resp, err := input.Client.UpdateImage(ctx, input.ProjectID, input.ClusterID, input.AppName, input.DeploymentTargetName, tag)
 	if err != nil {
-		return "", fmt.Errorf("unable to update image: %w", err)
+		return fmt.Errorf("unable to update image: %w", err)
 	}
 
 	triggeredBackgroundColor := color.FgGreen
 
 	_, _ = color.New(triggeredBackgroundColor).Printf("Updated application %s to use tag \"%s\"\n", input.AppName, tag)
-	return resp.RevisionID, nil
+
+	if input.WaitForSuccessfulUpdate {
+		return waitForAppRevisionStatus(ctx, waitForAppRevisionStatusInput{
+			ProjectID:  input.ProjectID,
+			ClusterID:  input.ClusterID,
+			AppName:    input.AppName,
+			RevisionID: resp.RevisionID,
+			Client:     input.Client,
+		})
+	}
+	return nil
 }