Skip to content

Commit

Permalink
add basic app rollback command (#3782)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Oct 10, 2023
1 parent 3f47bb6 commit 0ee6be1
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 0 deletions.
26 changes: 26 additions & 0 deletions api/client/porter_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 38 additions & 0 deletions cli/cmd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 [email protected]")
}

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:]

Expand Down
67 changes: 67 additions & 0 deletions cli/cmd/v2/rollback.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 0ee6be1

Please sign in to comment.