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

add basic app rollback command (#3782)

ianedwards 2 лет назад
Родитель
Сommit
0ee6be12d1
3 измененных файлов с 131 добавлено и 0 удалено
  1. 26 0
      api/client/porter_app.go
  2. 38 0
      cli/cmd/commands/app.go
  3. 67 0
      cli/cmd/v2/rollback.go

+ 26 - 0
api/client/porter_app.go

@@ -551,3 +551,29 @@ func (c *Client) UpdateImage(
 
 	return resp, err
 }
+
+// ListAppRevisions lists the last ten app revisions for a given app
+func (c *Client) ListAppRevisions(
+	ctx context.Context,
+	projectID, clusterID uint,
+	appName string,
+	deploymentTargetID string,
+) (*porter_app.ListAppRevisionsResponse, error) {
+	resp := &porter_app.ListAppRevisionsResponse{}
+
+	req := &porter_app.ListAppRevisionsRequest{
+		DeploymentTargetID: deploymentTargetID,
+	}
+
+	err := c.getRequest(
+		fmt.Sprintf(
+			"/projects/%d/clusters/%d/apps/%s/revisions",
+			projectID, clusterID,
+			appName,
+		),
+		req,
+		resp,
+	)
+
+	return resp, err
+}

+ 38 - 0
cli/cmd/commands/app.go

@@ -108,6 +108,17 @@ func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
 	)
 	appCmd.AddCommand(appUpdateTagCmd)
 
+	// appRollback represents the "porter app rollback" subcommand
+	appRollbackCmd := &cobra.Command{
+		Use:   "rollback [application]",
+		Args:  cobra.MinimumNArgs(1),
+		Short: "Rolls back an application to the last successful revision.",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return checkLoginAndRunWithConfig(cmd, cliConf, args, appRollback)
+		},
+	}
+	appCmd.AddCommand(appRollbackCmd)
+
 	return appCmd
 }
 
@@ -160,6 +171,33 @@ func appRunFlags(appRunCmd *cobra.Command) {
 	)
 }
 
+func appRollback(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
+	project, err := client.GetProject(ctx, cliConfig.Project)
+	if err != nil {
+		return fmt.Errorf("could not retrieve project from Porter API. Please contact support@porter.run")
+	}
+
+	if !project.ValidateApplyV2 {
+		return fmt.Errorf("rollback command is not enabled for this project")
+	}
+
+	appName := args[0]
+	if appName == "" {
+		return fmt.Errorf("app name must be specified")
+	}
+
+	err = v2.Rollback(ctx, v2.RollbackInput{
+		CLIConfig: cliConfig,
+		Client:    client,
+		AppName:   appName,
+	})
+	if err != nil {
+		return fmt.Errorf("failed to rollback app: %w", err)
+	}
+
+	return nil
+}
+
 func appRun(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
 	execArgs := args[1:]
 

+ 67 - 0
cli/cmd/v2/rollback.go

@@ -0,0 +1,67 @@
+package v2
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/fatih/color"
+	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/cli/cmd/config"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/porter_app"
+)
+
+// RollbackInput is the input for the Rollback function
+type RollbackInput struct {
+	// CLIConfig is the CLI configuration
+	CLIConfig config.CLIConfig
+	// Client is the Porter API client
+	Client api.Client
+	// AppName is the name of the app to rollback
+	AppName string
+}
+
+// Rollback deploys the previous successful revision of an app
+func Rollback(ctx context.Context, inp RollbackInput) error {
+	targetResp, err := inp.Client.DefaultDeploymentTarget(ctx, inp.CLIConfig.Project, inp.CLIConfig.Cluster)
+	if err != nil {
+		return fmt.Errorf("error calling default deployment target endpoint: %w", err)
+	}
+	deploymentTargetID := targetResp.DeploymentTargetID
+
+	listResp, err := inp.Client.ListAppRevisions(ctx, inp.CLIConfig.Project, inp.CLIConfig.Cluster, inp.AppName, deploymentTargetID)
+	if err != nil {
+		return fmt.Errorf("error calling current app revision endpoint: %w", err)
+	}
+	if len(listResp.AppRevisions) <= 1 {
+		return fmt.Errorf("no previous successful revisions found for app %s", inp.AppName)
+	}
+
+	revisions := listResp.AppRevisions
+	var rollbackTarget porter_app.Revision
+
+	for _, rev := range revisions[1:] {
+		if rev.RevisionNumber != 0 && rev.Status == models.AppRevisionStatus_Deployed {
+			rollbackTarget = rev
+			break
+		}
+	}
+	if rollbackTarget.ID == "" {
+		return fmt.Errorf("no previous successful revisions found for app %s", inp.AppName)
+	}
+
+	color.New(color.FgGreen).Printf("Rolling back to revision %d...\n", rollbackTarget.RevisionNumber) // nolint:errcheck,gosec
+
+	applyResp, err := inp.Client.ApplyPorterApp(ctx, inp.CLIConfig.Project, inp.CLIConfig.Cluster, rollbackTarget.B64AppProto, deploymentTargetID, "", false)
+	if err != nil {
+		return fmt.Errorf("error calling apply endpoint: %w", err)
+	}
+
+	if applyResp.CLIAction != porterv1.EnumCLIAction_ENUM_CLI_ACTION_NONE {
+		return fmt.Errorf("unexpected CLI action: %s", applyResp.CLIAction)
+	}
+
+	color.New(color.FgGreen).Printf("Successfully rolled back to revision %d\n", rollbackTarget.RevisionNumber) // nolint:errcheck,gosec
+	return nil
+}