Skip to content

Commit 5181d11

Browse files
authored
Add rollback app version (#33)
1 parent b669f49 commit 5181d11

File tree

7 files changed

+278
-11
lines changed

7 files changed

+278
-11
lines changed

apptrust/commands/flags.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ import (
88
)
99

1010
const (
11-
Ping = "ping"
12-
VersionCreate = "version-create"
13-
VersionPromote = "version-promote"
14-
VersionDelete = "version-delete"
15-
VersionRelease = "version-release"
16-
VersionUpdate = "version-update"
17-
PackageBind = "package-bind"
18-
PackageUnbind = "package-unbind"
19-
AppCreate = "app-create"
20-
AppUpdate = "app-update"
21-
AppDelete = "app-delete"
11+
Ping = "ping"
12+
VersionCreate = "version-create"
13+
VersionPromote = "version-promote"
14+
VersionRollback = "version-rollback"
15+
VersionDelete = "version-delete"
16+
VersionRelease = "version-release"
17+
VersionUpdate = "version-update"
18+
PackageBind = "package-bind"
19+
PackageUnbind = "package-unbind"
20+
AppCreate = "app-create"
21+
AppUpdate = "app-update"
22+
AppDelete = "app-delete"
2223
)
2324

2425
const (
@@ -129,6 +130,12 @@ var commandFlags = map[string][]string{
129130
accessToken,
130131
serverId,
131132
},
133+
VersionRollback: {
134+
url,
135+
user,
136+
accessToken,
137+
serverId,
138+
},
132139
VersionUpdate: {
133140
url,
134141
user,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package version
2+
3+
//go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE}
4+
5+
import (
6+
"github.com/jfrog/jfrog-cli-application/apptrust/app"
7+
"github.com/jfrog/jfrog-cli-application/apptrust/commands"
8+
"github.com/jfrog/jfrog-cli-application/apptrust/commands/utils"
9+
"github.com/jfrog/jfrog-cli-application/apptrust/common"
10+
"github.com/jfrog/jfrog-cli-application/apptrust/model"
11+
"github.com/jfrog/jfrog-cli-application/apptrust/service"
12+
"github.com/jfrog/jfrog-cli-application/apptrust/service/versions"
13+
commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands"
14+
pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common"
15+
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
16+
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
17+
)
18+
19+
type rollbackAppVersionCommand struct {
20+
versionService versions.VersionService
21+
serverDetails *coreConfig.ServerDetails
22+
applicationKey string
23+
version string
24+
requestPayload *model.RollbackAppVersionRequest
25+
fromStage string
26+
}
27+
28+
func (rv *rollbackAppVersionCommand) Run() error {
29+
ctx, err := service.NewContext(*rv.serverDetails)
30+
if err != nil {
31+
return err
32+
}
33+
34+
return rv.versionService.RollbackAppVersion(ctx, rv.applicationKey, rv.version, rv.requestPayload)
35+
}
36+
37+
func (rv *rollbackAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) {
38+
return rv.serverDetails, nil
39+
}
40+
41+
func (rv *rollbackAppVersionCommand) CommandName() string {
42+
return commands.VersionRollback
43+
}
44+
45+
func (rv *rollbackAppVersionCommand) prepareAndRunCommand(ctx *components.Context) error {
46+
if len(ctx.Arguments) != 3 {
47+
return pluginsCommon.WrongNumberOfArgumentsHandler(ctx)
48+
}
49+
50+
rv.applicationKey = ctx.Arguments[0]
51+
rv.version = ctx.Arguments[1]
52+
rv.fromStage = ctx.Arguments[2]
53+
54+
serverDetails, err := utils.ServerDetailsByFlags(ctx)
55+
if err != nil {
56+
return err
57+
}
58+
rv.serverDetails = serverDetails
59+
rv.requestPayload = model.NewRollbackAppVersionRequest(rv.fromStage)
60+
61+
return commonCLiCommands.Exec(rv)
62+
}
63+
64+
func GetRollbackAppVersionCommand(appContext app.Context) components.Command {
65+
cmd := &rollbackAppVersionCommand{
66+
versionService: appContext.GetVersionService(),
67+
}
68+
return components.Command{
69+
Name: commands.VersionRollback,
70+
Description: "Roll back application version promotion.",
71+
Category: common.CategoryVersion,
72+
Aliases: []string{"vrb"},
73+
Arguments: []components.Argument{
74+
{
75+
Name: "application-key",
76+
Description: "The application key.",
77+
Optional: false,
78+
},
79+
{
80+
Name: "version",
81+
Description: "The version to roll back.",
82+
Optional: false,
83+
},
84+
},
85+
Flags: commands.GetCommandFlags(commands.VersionRollback),
86+
Action: cmd.prepareAndRunCommand,
87+
}
88+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package version
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
mockversions "github.com/jfrog/jfrog-cli-application/apptrust/service/versions/mocks"
8+
"go.uber.org/mock/gomock"
9+
10+
"github.com/jfrog/jfrog-cli-application/apptrust/model"
11+
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestRollbackAppVersionCommand_Run(t *testing.T) {
16+
ctrl := gomock.NewController(t)
17+
defer ctrl.Finish()
18+
19+
serverDetails := &config.ServerDetails{Url: "https://example.com"}
20+
applicationKey := "video-encoder"
21+
version := "1.5.0"
22+
requestPayload := &model.RollbackAppVersionRequest{
23+
FromStage: "qa",
24+
}
25+
26+
mockVersionService := mockversions.NewMockVersionService(ctrl)
27+
mockVersionService.EXPECT().RollbackAppVersion(gomock.Any(), applicationKey, version, requestPayload).
28+
Return(nil).Times(1)
29+
30+
cmd := &rollbackAppVersionCommand{
31+
versionService: mockVersionService,
32+
serverDetails: serverDetails,
33+
applicationKey: applicationKey,
34+
version: version,
35+
requestPayload: requestPayload,
36+
}
37+
38+
err := cmd.Run()
39+
assert.NoError(t, err)
40+
}
41+
42+
func TestRollbackAppVersionCommand_Run_Error(t *testing.T) {
43+
ctrl := gomock.NewController(t)
44+
defer ctrl.Finish()
45+
46+
serverDetails := &config.ServerDetails{Url: "https://example.com"}
47+
applicationKey := "video-encoder"
48+
version := "1.5.0"
49+
requestPayload := &model.RollbackAppVersionRequest{
50+
FromStage: "qa",
51+
}
52+
expectedError := errors.New("rollback service error occurred")
53+
54+
mockVersionService := mockversions.NewMockVersionService(ctrl)
55+
mockVersionService.EXPECT().RollbackAppVersion(gomock.Any(), applicationKey, version, requestPayload).
56+
Return(expectedError).Times(1)
57+
58+
cmd := &rollbackAppVersionCommand{
59+
versionService: mockVersionService,
60+
serverDetails: serverDetails,
61+
applicationKey: applicationKey,
62+
version: version,
63+
requestPayload: requestPayload,
64+
}
65+
66+
err := cmd.Run()
67+
assert.Error(t, err)
68+
assert.Contains(t, err.Error(), "rollback service error occurred")
69+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package model
2+
3+
type RollbackAppVersionRequest struct {
4+
FromStage string `json:"from_stage"`
5+
}
6+
7+
type RollbackAppVersionResponse struct {
8+
ApplicationKey string `json:"application_key"`
9+
Version string `json:"version"`
10+
ProjectKey string `json:"project_key"`
11+
RollbackFromStage string `json:"rollback_from_stage"`
12+
RollbackToStage string `json:"rollback_to_stage"`
13+
}
14+
15+
func NewRollbackAppVersionRequest(fromStage string) *RollbackAppVersionRequest {
16+
return &RollbackAppVersionRequest{
17+
FromStage: fromStage,
18+
}
19+
}

apptrust/service/versions/version_service.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type VersionService interface {
1616
CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest) error
1717
PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) error
1818
ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) error
19+
RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest) error
1920
DeleteAppVersion(ctx service.Context, applicationKey string, version string) error
2021
UpdateAppVersion(ctx service.Context, applicationKey string, version string, request *model.UpdateAppVersionRequest) error
2122
}
@@ -71,6 +72,21 @@ func (vs *versionService) ReleaseAppVersion(ctx service.Context, applicationKey,
7172
return nil
7273
}
7374

75+
func (vs *versionService) RollbackAppVersion(ctx service.Context, applicationKey, version string, request *model.RollbackAppVersionRequest) error {
76+
endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s/rollback", applicationKey, version)
77+
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{})
78+
if err != nil {
79+
return err
80+
}
81+
82+
if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusAccepted {
83+
return fmt.Errorf("failed to rollback app version. Status code: %d. \n%s",
84+
response.StatusCode, responseBody)
85+
}
86+
87+
return nil
88+
}
89+
7490
func (vs *versionService) DeleteAppVersion(ctx service.Context, applicationKey, version string) error {
7591
url := fmt.Sprintf("/v1/applications/%s/versions/%s", applicationKey, version)
7692
response, responseBody, err := ctx.GetHttpClient().Delete(url)

apptrust/service/versions/version_service_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,3 +415,70 @@ func TestUpdateAppVersion(t *testing.T) {
415415
})
416416
}
417417
}
418+
419+
func TestRollbackAppVersion(t *testing.T) {
420+
tests := []struct {
421+
name string
422+
applicationKey string
423+
version string
424+
payload *model.RollbackAppVersionRequest
425+
expectedStatus int
426+
expectedError bool
427+
}{
428+
{
429+
name: "successful rollback with 200",
430+
applicationKey: "video-encoder",
431+
version: "1.5.0",
432+
payload: &model.RollbackAppVersionRequest{
433+
FromStage: "qa",
434+
},
435+
expectedStatus: http.StatusOK,
436+
expectedError: false,
437+
},
438+
{
439+
name: "successful rollback with 204",
440+
applicationKey: "video-encoder",
441+
version: "1.5.0",
442+
payload: &model.RollbackAppVersionRequest{
443+
FromStage: "prod",
444+
},
445+
expectedStatus: http.StatusAccepted,
446+
expectedError: false,
447+
},
448+
{
449+
name: "failed rollback - bad request",
450+
applicationKey: "invalid-app",
451+
version: "1.0.0",
452+
payload: &model.RollbackAppVersionRequest{
453+
FromStage: "nonexistent",
454+
},
455+
expectedStatus: http.StatusBadRequest,
456+
expectedError: true,
457+
},
458+
}
459+
460+
for _, tt := range tests {
461+
t.Run(tt.name, func(t *testing.T) {
462+
ctrl := gomock.NewController(t)
463+
defer ctrl.Finish()
464+
465+
mockCtx := mockservice.NewMockContext(ctrl)
466+
mockClient := mockhttp.NewMockApptrustHttpClient(ctrl)
467+
mockCtx.EXPECT().GetHttpClient().Return(mockClient)
468+
469+
expectedEndpoint := "/v1/applications/" + tt.applicationKey + "/versions/" + tt.version + "/rollback"
470+
mockClient.EXPECT().Post(expectedEndpoint, tt.payload, map[string]string{}).
471+
Return(&http.Response{StatusCode: tt.expectedStatus}, []byte(""), nil)
472+
473+
service := NewVersionService()
474+
err := service.RollbackAppVersion(mockCtx, tt.applicationKey, tt.version, tt.payload)
475+
476+
if tt.expectedError {
477+
assert.Error(t, err)
478+
assert.Contains(t, err.Error(), "failed to rollback app version")
479+
} else {
480+
assert.NoError(t, err)
481+
}
482+
})
483+
}
484+
}

cli/cli.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func GetJfrogCliApptrustApp() components.App {
4545
system.GetPingCommand(appContext),
4646
version.GetCreateAppVersionCommand(appContext),
4747
version.GetPromoteAppVersionCommand(appContext),
48+
version.GetRollbackAppVersionCommand(appContext),
4849
version.GetReleaseAppVersionCommand(appContext),
4950
version.GetDeleteAppVersionCommand(appContext),
5051
version.GetUpdateAppVersionCommand(appContext),

0 commit comments

Comments
 (0)