Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import (
)

const (
Ping = "ping"
VersionCreate = "version-create"
VersionPromote = "version-promote"
VersionDelete = "version-delete"
VersionRelease = "version-release"
VersionUpdate = "version-update"
PackageBind = "package-bind"
PackageUnbind = "package-unbind"
AppCreate = "app-create"
AppUpdate = "app-update"
AppDelete = "app-delete"
Ping = "ping"
VersionCreate = "version-create"
VersionPromote = "version-promote"
VersionRollback = "version-rollback"
VersionDelete = "version-delete"
VersionRelease = "version-release"
VersionUpdate = "version-update"
PackageBind = "package-bind"
PackageUnbind = "package-unbind"
AppCreate = "app-create"
AppUpdate = "app-update"
AppDelete = "app-delete"
)

const (
Expand Down Expand Up @@ -129,6 +130,12 @@ var commandFlags = map[string][]string{
accessToken,
serverId,
},
VersionRollback: {
url,
user,
accessToken,
serverId,
},
VersionUpdate: {
url,
user,
Expand Down
88 changes: 88 additions & 0 deletions apptrust/commands/version/rollback_app_version_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package version

//go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE}

import (
"github.com/jfrog/jfrog-cli-application/apptrust/app"
"github.com/jfrog/jfrog-cli-application/apptrust/commands"
"github.com/jfrog/jfrog-cli-application/apptrust/commands/utils"
"github.com/jfrog/jfrog-cli-application/apptrust/common"
"github.com/jfrog/jfrog-cli-application/apptrust/model"
"github.com/jfrog/jfrog-cli-application/apptrust/service"
"github.com/jfrog/jfrog-cli-application/apptrust/service/versions"
commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands"
pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
)

type rollbackAppVersionCommand struct {
versionService versions.VersionService
serverDetails *coreConfig.ServerDetails
applicationKey string
version string
requestPayload *model.RollbackAppVersionRequest
fromStage string
}

func (rv *rollbackAppVersionCommand) Run() error {
ctx, err := service.NewContext(*rv.serverDetails)
if err != nil {
return err
}

return rv.versionService.RollbackAppVersion(ctx, rv.applicationKey, rv.version, rv.requestPayload)
}

func (rv *rollbackAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) {
return rv.serverDetails, nil
}

func (rv *rollbackAppVersionCommand) CommandName() string {
return commands.VersionRollback
}

func (rv *rollbackAppVersionCommand) prepareAndRunCommand(ctx *components.Context) error {
if len(ctx.Arguments) != 3 {
return pluginsCommon.WrongNumberOfArgumentsHandler(ctx)
}

rv.applicationKey = ctx.Arguments[0]
rv.version = ctx.Arguments[1]
rv.fromStage = ctx.Arguments[2]

serverDetails, err := utils.ServerDetailsByFlags(ctx)
if err != nil {
return err
}
rv.serverDetails = serverDetails
rv.requestPayload = model.NewRollbackAppVersionRequest(rv.fromStage)

return commonCLiCommands.Exec(rv)
}

func GetRollbackAppVersionCommand(appContext app.Context) components.Command {
cmd := &rollbackAppVersionCommand{
versionService: appContext.GetVersionService(),
}
return components.Command{
Name: commands.VersionRollback,
Description: "Roll back application version promotion.",
Category: common.CategoryVersion,
Aliases: []string{"vrb"},
Arguments: []components.Argument{
{
Name: "application-key",
Description: "The application key.",
Optional: false,
},
{
Name: "version",
Description: "The version to roll back.",
Optional: false,
},
},
Flags: commands.GetCommandFlags(commands.VersionRollback),
Action: cmd.prepareAndRunCommand,
}
}
69 changes: 69 additions & 0 deletions apptrust/commands/version/rollback_app_version_cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package version

import (
"errors"
"testing"

mockversions "github.com/jfrog/jfrog-cli-application/apptrust/service/versions/mocks"
"go.uber.org/mock/gomock"

"github.com/jfrog/jfrog-cli-application/apptrust/model"
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/stretchr/testify/assert"
)

func TestRollbackAppVersionCommand_Run(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

serverDetails := &config.ServerDetails{Url: "https://example.com"}
applicationKey := "video-encoder"
version := "1.5.0"
requestPayload := &model.RollbackAppVersionRequest{
FromStage: "qa",
}

mockVersionService := mockversions.NewMockVersionService(ctrl)
mockVersionService.EXPECT().RollbackAppVersion(gomock.Any(), applicationKey, version, requestPayload).
Return(nil).Times(1)

cmd := &rollbackAppVersionCommand{
versionService: mockVersionService,
serverDetails: serverDetails,
applicationKey: applicationKey,
version: version,
requestPayload: requestPayload,
}

err := cmd.Run()
assert.NoError(t, err)
}

func TestRollbackAppVersionCommand_Run_Error(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

serverDetails := &config.ServerDetails{Url: "https://example.com"}
applicationKey := "video-encoder"
version := "1.5.0"
requestPayload := &model.RollbackAppVersionRequest{
FromStage: "qa",
}
expectedError := errors.New("rollback service error occurred")

mockVersionService := mockversions.NewMockVersionService(ctrl)
mockVersionService.EXPECT().RollbackAppVersion(gomock.Any(), applicationKey, version, requestPayload).
Return(expectedError).Times(1)

cmd := &rollbackAppVersionCommand{
versionService: mockVersionService,
serverDetails: serverDetails,
applicationKey: applicationKey,
version: version,
requestPayload: requestPayload,
}

err := cmd.Run()
assert.Error(t, err)
assert.Contains(t, err.Error(), "rollback service error occurred")
}
19 changes: 19 additions & 0 deletions apptrust/model/rollback_app_version_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package model

type RollbackAppVersionRequest struct {
FromStage string `json:"from_stage"`
}

type RollbackAppVersionResponse struct {
ApplicationKey string `json:"application_key"`
Version string `json:"version"`
ProjectKey string `json:"project_key"`
RollbackFromStage string `json:"rollback_from_stage"`
RollbackToStage string `json:"rollback_to_stage"`
}

func NewRollbackAppVersionRequest(fromStage string) *RollbackAppVersionRequest {
return &RollbackAppVersionRequest{
FromStage: fromStage,
}
}
16 changes: 16 additions & 0 deletions apptrust/service/versions/version_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type VersionService interface {
CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest) error
PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) error
ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) error
RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest) error
DeleteAppVersion(ctx service.Context, applicationKey string, version string) error
UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) error
}
Expand Down Expand Up @@ -71,6 +72,21 @@ func (vs *versionService) ReleaseAppVersion(ctx service.Context, applicationKey,
return nil
}

func (vs *versionService) RollbackAppVersion(ctx service.Context, applicationKey, version string, request *model.RollbackAppVersionRequest) error {
endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s/rollback", applicationKey, version)
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{})
if err != nil {
return err
}

if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusAccepted {
return fmt.Errorf("failed to rollback app version. Status code: %d. \n%s",
response.StatusCode, responseBody)
}

return nil
}

func (vs *versionService) DeleteAppVersion(ctx service.Context, applicationKey, version string) error {
url := fmt.Sprintf("/v1/applications/%s/versions/%s", applicationKey, version)
response, responseBody, err := ctx.GetHttpClient().Delete(url)
Expand Down
67 changes: 67 additions & 0 deletions apptrust/service/versions/version_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,70 @@ func TestUpdateAppVersion(t *testing.T) {
})
}
}

func TestRollbackAppVersion(t *testing.T) {
tests := []struct {
name string
applicationKey string
version string
payload *model.RollbackAppVersionRequest
expectedStatus int
expectedError bool
}{
{
name: "successful rollback with 200",
applicationKey: "video-encoder",
version: "1.5.0",
payload: &model.RollbackAppVersionRequest{
FromStage: "qa",
},
expectedStatus: http.StatusOK,
expectedError: false,
},
{
name: "successful rollback with 204",
applicationKey: "video-encoder",
version: "1.5.0",
payload: &model.RollbackAppVersionRequest{
FromStage: "prod",
},
expectedStatus: http.StatusAccepted,
expectedError: false,
},
{
name: "failed rollback - bad request",
applicationKey: "invalid-app",
version: "1.0.0",
payload: &model.RollbackAppVersionRequest{
FromStage: "nonexistent",
},
expectedStatus: http.StatusBadRequest,
expectedError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockCtx := mockservice.NewMockContext(ctrl)
mockClient := mockhttp.NewMockApptrustHttpClient(ctrl)
mockCtx.EXPECT().GetHttpClient().Return(mockClient)

expectedEndpoint := "/v1/applications/" + tt.applicationKey + "/versions/" + tt.version + "/rollback"
mockClient.EXPECT().Post(expectedEndpoint, tt.payload, map[string]string{}).
Return(&http.Response{StatusCode: tt.expectedStatus}, []byte(""), nil)

service := NewVersionService()
err := service.RollbackAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload)

if tt.expectedError {
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to rollback app version")
} else {
assert.NoError(t, err)
}
})
}
}
1 change: 1 addition & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func GetJfrogCliApptrustApp() components.App {
system.GetPingCommand(appContext),
version.GetCreateAppVersionCommand(appContext),
version.GetPromoteAppVersionCommand(appContext),
version.GetRollbackAppVersionCommand(appContext),
version.GetReleaseAppVersionCommand(appContext),
version.GetDeleteAppVersionCommand(appContext),
version.GetUpdateAppVersionCommand(appContext),
Expand Down
Loading