Skip to content

Commit

Permalink
Merge pull request #1086 from aziontech/rollback-static-files
Browse files Browse the repository at this point in the history
feat: add rollback command
  • Loading branch information
PatrickMenoti authored Feb 13, 2025
2 parents 8d2b5a9 + ca5669a commit 3938b70
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 5 deletions.
9 changes: 9 additions & 0 deletions messages/rollback/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package rollback

import "errors"

var (
ERRORROLLBACK = errors.New("Failed to roll back to previous static files")
ERRORNEEDSDEPLOY = errors.New("You cannot use the rollback command unless you have already deployed this project. Please check if you are in the correct working directory")
ERRORAZION = errors.New("Failed to open the azion.json file. The file doesn't exist, is corrupted, or has an invalid JSON format")
)
12 changes: 12 additions & 0 deletions messages/rollback/messages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package rollback

const (
USAGE = "rollback"
SHORTDESCRIPTION = "Sets static files from a previous deploy"
LONGDESCRIPTION = "Sets static files from a previous deploy within the same bucket"
FLAGHELP = "Displays more information about the rollback command"
FLAGORIGINKEY = "Origin key of the origin used for storage of static files"
CONFFLAG = "Relative path to where your custom azion.json and args.json files are stored"
ASKORIGIN = "Enter the key of the Origin you wish to update:"
SUCCESS = "Static files rolled back successfully"
)
8 changes: 7 additions & 1 deletion pkg/api/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,13 @@ func (c *Client) ListObject(ctx context.Context, bucketName string, opts *contra
MaxObjectCount(int32(opts.PageSize)).ContinuationToken(opts.ContinuationToken)
resp, httpResp, err := req.Execute()
if err != nil {
logger.Error("Error while listing objects", zap.Error(err))
if httpResp != nil {
logger.Debug("Error while listing Objects from Bucket", zap.Error(err))
err := utils.LogAndRewindBody(httpResp)
if err != nil {
return nil, err
}
}
return nil, utils.ErrorPerStatusCode(httpResp, err)
}
return resp, nil
Expand Down
5 changes: 1 addition & 4 deletions pkg/cmd/list/edge_storage/object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ func (b *Objects) RunE(cmd *cobra.Command, args []string) error {
}
b.BucketName = answer
}
client := api.NewClient(
b.Factory.HttpClient,
b.Factory.Config.GetString("storage_url"),
b.Factory.Config.GetString("token"))
client := api.NewClient(b.Factory.HttpClient, b.Factory.Config.GetString("storage_url"), b.Factory.Config.GetString("token"))
return b.PrintTable(client)
}

Expand Down
141 changes: 141 additions & 0 deletions pkg/cmd/rollback/rollback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package rollback

import (
"context"
"fmt"
"strings"

"github.com/MakeNowJust/heredoc"
msg "github.com/aziontech/azion-cli/messages/rollback"
apiOrigin "github.com/aziontech/azion-cli/pkg/api/origin"
api "github.com/aziontech/azion-cli/pkg/api/storage"
"github.com/aziontech/azion-cli/pkg/cmdutil"
"github.com/aziontech/azion-cli/pkg/contracts"
"github.com/aziontech/azion-cli/pkg/logger"
"github.com/aziontech/azion-cli/pkg/output"
"github.com/aziontech/azion-cli/utils"
"github.com/spf13/cobra"
"go.uber.org/zap"
)

var (
originKey string
projectPath string
)

type RollbackCmd struct {
AskInput func(string) (string, error)
GetAzionJsonContent func(pathConf string) (*contracts.AzionApplicationOptions, error)
WriteAzionJsonContent func(conf *contracts.AzionApplicationOptions, confPath string) error
}

func NewDeleteCmd(f *cmdutil.Factory) *RollbackCmd {
return &RollbackCmd{
GetAzionJsonContent: utils.GetAzionJsonContent,
WriteAzionJsonContent: utils.WriteAzionJsonContent,
AskInput: utils.AskInput,
}
}

func NewCobraCmd(rollback *RollbackCmd, f *cmdutil.Factory) *cobra.Command {
cobraCmd := &cobra.Command{
Use: msg.USAGE,
Short: msg.SHORTDESCRIPTION,
Long: msg.LONGDESCRIPTION,
SilenceUsage: true,
SilenceErrors: true,
Example: heredoc.Doc(`
$ azion rollback --origin-key aaaa-bbbb-cccc-dddd
`),
RunE: func(cmd *cobra.Command, args []string) error {

if !cmd.Flags().Changed("origin-key") {
answer, err := rollback.AskInput(msg.ASKORIGIN)
if err != nil {
return err
}
originKey = answer
}

conf, err := rollback.GetAzionJsonContent(projectPath)
if err != nil {
logger.Debug("Error while reading azion.json file", zap.Error(err))
return msg.ERRORAZION
}

if conf.Bucket == "" || conf.Prefix == "" {
return msg.ERRORNEEDSDEPLOY
}

timestamp, err := checkForNewTimestamp(f, conf.Prefix, conf.Bucket)
if err != nil {
return msg.ERRORROLLBACK
}

clientOrigin := apiOrigin.NewClient(f.HttpClient, f.Config.GetString("api_url"), f.Config.GetString("token"))
request := apiOrigin.UpdateRequest{}
request.SetPrefix(timestamp)

_, err = clientOrigin.Update(context.Background(), conf.Application.ID, originKey, &request)
if err != nil {
return msg.ERRORROLLBACK
}

conf.Prefix = timestamp
err = rollback.WriteAzionJsonContent(conf, projectPath)
if err != nil {
return msg.ERRORROLLBACK
}

rollbackOut := output.GeneralOutput{
Msg: msg.SUCCESS,
Out: f.IOStreams.Out,
Flags: f.Flags,
}
return output.Print(&rollbackOut)
},
}

cobraCmd.Flags().StringVar(&originKey, "origin-key", "", msg.FLAGORIGINKEY)
cobraCmd.Flags().StringVar(&projectPath, "config-dir", "azion", msg.CONFFLAG)
cobraCmd.Flags().BoolP("help", "h", false, msg.FLAGHELP)

return cobraCmd
}

func NewCmd(f *cmdutil.Factory) *cobra.Command {
return NewCobraCmd(NewDeleteCmd(f), f)
}

func checkForNewTimestamp(f *cmdutil.Factory, referenceTimestamp, bucketName string) (string, error) {
logger.Debug("Checking if there are previous static files for the following bucket", zap.Any("Bucket name", bucketName))
client := api.NewClient(f.HttpClient, f.Config.GetString("storage_url"), f.Config.GetString("token"))
c := context.Background()
options := &contracts.ListOptions{
// Sort: "desc",
// OrderBy: "last_modified",
PageSize: 100000,
}

resp, err := client.ListObject(c, bucketName, options)
if err != nil {
fmt.Println(err.Error())
return "", err
}

var prevTimestamp string
for _, object := range resp.Results {
parts := strings.Split(object.Key, "/")
if len(parts) > 1 {
timestamp := parts[0]
if timestamp == referenceTimestamp {
return prevTimestamp, nil
} else {
prevTimestamp = timestamp
continue
}
}
}

return referenceTimestamp, nil
}
119 changes: 119 additions & 0 deletions pkg/cmd/rollback/rollback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package rollback

import (
"fmt"
"testing"

"github.com/aziontech/azion-cli/pkg/contracts"
"github.com/aziontech/azion-cli/pkg/logger"
"github.com/aziontech/azion-cli/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/aziontech/azion-cli/pkg/httpmock"
"github.com/aziontech/azion-cli/pkg/testutils"
)

func mockInvalidOriginKey(msg string) (string, error) {
return "invalid", nil
}

func mockParseError(msg string) (string, error) {
return "invalid", utils.ErrorParseResponse
}

func TestRollbackWithAskInput(t *testing.T) {
logger.New(zapcore.DebugLevel)

tests := []struct {
name string
originKey string
method string
endpoint string
statusCode int
responseBody string
expectedOutput string
expectError bool
mockInputs func(string) (string, error)
mockError error
}{
{
name: "rollback with invalid origin key",
originKey: "invalid",
method: "UPDATE",
endpoint: "origins/invalid",
statusCode: 400,
responseBody: "Bad Request",
expectedOutput: "",
expectError: true,
mockInputs: mockInvalidOriginKey,
mockError: fmt.Errorf("Failed to parse your response. Check your response and try again. If the error persists, contact Azion support"),
},
{
name: "error in input",
originKey: "invalid",
method: "UPDATE",
endpoint: "origins/invalid",
statusCode: 400,
responseBody: "Bad Request",
expectedOutput: "",
expectError: true,
mockInputs: mockInvalidOriginKey,
mockError: fmt.Errorf("invalid argument \"\" for \"--origin-key\" flag: invalid syntax"),
},
{
name: "error - parse answer",
originKey: "",
method: "",
endpoint: "",
statusCode: 0,
responseBody: "",
expectedOutput: "",
expectError: true,
mockInputs: mockParseError,
mockError: utils.ErrorParseResponse,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &httpmock.Registry{}
mock.Register(
httpmock.REST(tt.method, tt.endpoint),
httpmock.StatusStringResponse(tt.statusCode, tt.responseBody),
)

f, stdout, _ := testutils.NewFactory(mock)

rollbackCmd := NewDeleteCmd(f)
rollbackCmd.AskInput = tt.mockInputs
rollbackCmd.GetAzionJsonContent = func(pathConf string) (*contracts.AzionApplicationOptions, error) {
return &contracts.AzionApplicationOptions{
Application: contracts.AzionJsonDataApplication{
ID: 0001110001,
Name: "namezin",
},
Bucket: "nomedobucket",
Prefix: "001001001",
}, nil
}
rollbackCmd.WriteAzionJsonContent = func(conf *contracts.AzionApplicationOptions, confPath string) error {
return nil
}
cobraCmd := NewCobraCmd(rollbackCmd, f)

if tt.originKey != "" {
cobraCmd.SetArgs([]string{"--origin-key", tt.originKey})
}

_, err := cobraCmd.ExecuteC()
if tt.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expectedOutput, stdout.String())
}
})
}
}
2 changes: 2 additions & 0 deletions pkg/cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
logcmd "github.com/aziontech/azion-cli/pkg/cmd/logs"
"github.com/aziontech/azion-cli/pkg/cmd/purge"
"github.com/aziontech/azion-cli/pkg/cmd/reset"
"github.com/aziontech/azion-cli/pkg/cmd/rollback"
"github.com/aziontech/azion-cli/pkg/cmd/sync"
"github.com/aziontech/azion-cli/pkg/cmd/unlink"
"github.com/aziontech/azion-cli/pkg/cmd/update"
Expand Down Expand Up @@ -124,6 +125,7 @@ func (fact *factoryRoot) setCmds(cobraCmd *cobra.Command) {
cobraCmd.AddCommand(purge.NewCmd(fact.factory))
cobraCmd.AddCommand(reset.NewCmd(fact.factory))
cobraCmd.AddCommand(sync.NewCmd(fact.factory))
cobraCmd.AddCommand(rollback.NewCmd(fact.factory))
}

func (fact *factoryRoot) CmdRoot() cmdutil.Command {
Expand Down

0 comments on commit 3938b70

Please sign in to comment.