Skip to content

Commit e6ef7e9

Browse files
committed
APP-1823 - Support dry run in application CLI for create version
1 parent ae4e5d1 commit e6ef7e9

8 files changed

Lines changed: 120 additions & 56 deletions

File tree

apptrust/commands/flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ var commandFlags = map[string][]string{
123123
IncludeFilterFlag,
124124
ExcludeFilterFlag,
125125
SpecVarsFlag,
126+
DryRunFlag,
126127
},
127128
VersionPromote: {
128129
url,

apptrust/commands/version/create_app_version_cmd.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type createAppVersionCommand struct {
2121
serverDetails *coreConfig.ServerDetails
2222
requestPayload *model.CreateAppVersionRequest
2323
sync bool
24+
dryRun bool
2425
}
2526

2627
func (cv *createAppVersionCommand) Run() error {
@@ -29,7 +30,7 @@ func (cv *createAppVersionCommand) Run() error {
2930
return err
3031
}
3132

32-
return cv.versionService.CreateAppVersion(ctx, cv.requestPayload, cv.sync)
33+
return cv.versionService.CreateAppVersion(ctx, cv.requestPayload, cv.sync, cv.dryRun)
3334
}
3435

3536
func (cv *createAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) {
@@ -54,6 +55,7 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context)
5455
if errorutils.CheckError(err) != nil {
5556
return err
5657
}
58+
cv.dryRun = ctx.GetBoolFlagValue(commands.DryRunFlag)
5759
return commonCLiCommands.Exec(cv)
5860
}
5961

apptrust/commands/version/create_app_version_cmd_test.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func TestCreateAppVersionCommand(t *testing.T) {
1818
tests := []struct {
1919
name string
2020
request *model.CreateAppVersionRequest
21+
dryRun bool
2122
shouldError bool
2223
errorMessage string
2324
}{
@@ -36,10 +37,28 @@ func TestCreateAppVersionCommand(t *testing.T) {
3637
}},
3738
},
3839
},
40+
dryRun: false,
41+
},
42+
{
43+
name: "success with dry-run",
44+
request: &model.CreateAppVersionRequest{
45+
ApplicationKey: "app-key",
46+
Version: "1.0.0",
47+
Sources: &model.CreateVersionSources{
48+
Packages: []model.CreateVersionPackage{{
49+
Type: "type",
50+
Name: "name",
51+
Version: "1.0.0",
52+
Repository: "repo",
53+
}},
54+
},
55+
},
56+
dryRun: true,
3957
},
4058
{
4159
name: "context error",
4260
request: &model.CreateAppVersionRequest{ApplicationKey: "app-key", Version: "1.0.0", Draft: false, Sources: &model.CreateVersionSources{Packages: []model.CreateVersionPackage{{Type: "type", Name: "name", Version: "1.0.0", Repository: "repo"}}}},
61+
dryRun: false,
4362
shouldError: true,
4463
errorMessage: "context error",
4564
},
@@ -57,10 +76,10 @@ func TestCreateAppVersionCommand(t *testing.T) {
5776

5877
mockVersionService := mockversions.NewMockVersionService(ctrl)
5978
if tt.shouldError {
60-
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true).
79+
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun).
6180
Return(errors.New(tt.errorMessage)).Times(1)
6281
} else {
63-
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true).
82+
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun).
6483
Return(nil).Times(1)
6584
}
6685

@@ -69,6 +88,7 @@ func TestCreateAppVersionCommand(t *testing.T) {
6988
serverDetails: &config.ServerDetails{Url: "https://example.com"},
7089
requestPayload: tt.request,
7190
sync: true,
91+
dryRun: tt.dryRun,
7292
}
7393

7494
err := cmd.Run()
@@ -196,8 +216,8 @@ func TestCreateAppVersionCommand_FlagsSuite(t *testing.T) {
196216
var actualPayload *model.CreateAppVersionRequest
197217
mockVersionService := mockversions.NewMockVersionService(ctrl)
198218
if !tt.expectsError {
199-
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any()).
200-
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, _ bool) error {
219+
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
220+
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, _ bool, _ bool) error {
201221
actualPayload = req
202222
return nil
203223
}).Times(1)
@@ -807,8 +827,8 @@ func TestCreateAppVersionCommand_SpecFileSuite(t *testing.T) {
807827
var capturedSync bool
808828
mockVersionService := mockversions.NewMockVersionService(ctrl)
809829
if !tt.expectsError {
810-
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any()).
811-
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync bool) error {
830+
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
831+
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) error {
812832
actualPayload = req
813833
capturedSync = sync
814834
return nil
@@ -876,8 +896,8 @@ func TestCreateAppVersionCommand_SyncFlagSuite(t *testing.T) {
876896

877897
var capturedSync bool
878898
mockVersionService := mockversions.NewMockVersionService(ctrl)
879-
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any()).
880-
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync bool) error {
899+
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
900+
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) error {
881901
capturedSync = sync
882902
return nil
883903
}).Times(1)

apptrust/http/http_utils.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package http
2+
3+
func IsSuccessStatusCode(statusCode int) bool {
4+
return statusCode >= 200 && statusCode < 300
5+
}

apptrust/service/versions/mocks/version_service_mock.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apptrust/service/versions/version_service.go

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import (
77
"net/http"
88
"strconv"
99

10+
apphttp "github.com/jfrog/jfrog-cli-application/apptrust/http"
1011
"github.com/jfrog/jfrog-cli-application/apptrust/service"
1112
"github.com/jfrog/jfrog-client-go/utils/log"
1213

1314
"github.com/jfrog/jfrog-cli-application/apptrust/model"
1415
)
1516

1617
type VersionService interface {
17-
CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync bool) error
18+
CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) error
1819
PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) error
1920
ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) error
2021
RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) error
@@ -29,24 +30,20 @@ func NewVersionService() VersionService {
2930
return &versionService{}
3031
}
3132

32-
func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync bool) error {
33+
func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) error {
3334
endpoint := fmt.Sprintf("/v1/applications/%s/versions/", request.ApplicationKey)
34-
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{"async": strconv.FormatBool(!sync)})
35+
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request,
36+
map[string]string{"async": strconv.FormatBool(!sync), "dry_run": strconv.FormatBool(dryRun)})
3537
if err != nil {
3638
return err
3739
}
3840

39-
expectedStatusCode := http.StatusCreated
40-
if !sync {
41-
expectedStatusCode = http.StatusAccepted
42-
}
43-
44-
if response.StatusCode != expectedStatusCode {
41+
if !apphttp.IsSuccessStatusCode(response.StatusCode) {
4542
return fmt.Errorf("failed to create app version. Status code: %d. \n%s",
4643
response.StatusCode, responseBody)
4744
}
4845

49-
log.Info("Application version created successfully.")
46+
logSuccessMessage(sync, request, dryRun)
5047
log.Output(string(responseBody))
5148
return nil
5249
}
@@ -58,7 +55,7 @@ func (vs *versionService) PromoteAppVersion(ctx service.Context, applicationKey,
5855
return err
5956
}
6057

61-
if response.StatusCode >= http.StatusBadRequest {
58+
if !apphttp.IsSuccessStatusCode(response.StatusCode) {
6259
return fmt.Errorf("failed to promote app version. Status code: %d. \n%s",
6360
response.StatusCode, responseBody)
6461
}
@@ -74,7 +71,7 @@ func (vs *versionService) ReleaseAppVersion(ctx service.Context, applicationKey,
7471
return err
7572
}
7673

77-
if response.StatusCode >= http.StatusBadRequest {
74+
if !apphttp.IsSuccessStatusCode(response.StatusCode) {
7875
return fmt.Errorf("failed to release app version. Status code: %d. \n%s",
7976
response.StatusCode, responseBody)
8077
}
@@ -90,13 +87,7 @@ func (vs *versionService) RollbackAppVersion(ctx service.Context, applicationKey
9087
return err
9188
}
9289

93-
// Validate status code based on sync mode
94-
expectedStatusCode := http.StatusAccepted
95-
if sync {
96-
expectedStatusCode = http.StatusOK
97-
}
98-
99-
if response.StatusCode != expectedStatusCode {
90+
if !apphttp.IsSuccessStatusCode(response.StatusCode) {
10091
return fmt.Errorf("failed to rollback app version. Status code: %d. \n%s",
10192
response.StatusCode, responseBody)
10293
}
@@ -165,3 +156,13 @@ func (vs *versionService) UpdateAppVersionSources(ctx service.Context, applicati
165156
log.Output(string(responseBody))
166157
return nil
167158
}
159+
160+
func logSuccessMessage(sync bool, request *model.CreateAppVersionRequest, dryRun bool) {
161+
if !sync {
162+
log.Info(fmt.Sprintf("Application version creation initiated: %s:%s", request.ApplicationKey, request.Version))
163+
} else if dryRun {
164+
log.Info(fmt.Sprintf("Dry run successful for application version: %s:%s", request.ApplicationKey, request.Version))
165+
} else {
166+
log.Info(fmt.Sprintf("Application version created successfully: %s:%s", request.ApplicationKey, request.Version))
167+
}
168+
}

apptrust/service/versions/version_service_test.go

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func TestCreateAppVersion(t *testing.T) {
2424
name string
2525
request *model.CreateAppVersionRequest
2626
sync bool
27+
dryRun bool
2728
mockResponse *http.Response
2829
mockResponseBody string
2930
mockError error
@@ -33,15 +34,37 @@ func TestCreateAppVersion(t *testing.T) {
3334
name: "success",
3435
request: &model.CreateAppVersionRequest{ApplicationKey: "test-app", Version: "1.0.0"},
3536
sync: true,
37+
dryRun: false,
3638
mockResponse: &http.Response{StatusCode: 201},
3739
mockResponseBody: "{}",
3840
mockError: nil,
3941
expectedError: "",
4042
},
43+
{
44+
name: "success with dry-run (200 OK)",
45+
request: &model.CreateAppVersionRequest{ApplicationKey: "test-app", Version: "1.0.0"},
46+
sync: true,
47+
dryRun: true,
48+
mockResponse: &http.Response{StatusCode: 200},
49+
mockResponseBody: "{\"validation\": \"passed\"}",
50+
mockError: nil,
51+
expectedError: "",
52+
},
4153
{
4254
name: "success with sync=false",
4355
request: &model.CreateAppVersionRequest{ApplicationKey: "test-app", Version: "1.0.0"},
4456
sync: false,
57+
dryRun: false,
58+
mockResponse: &http.Response{StatusCode: 202},
59+
mockResponseBody: "{}",
60+
mockError: nil,
61+
expectedError: "",
62+
},
63+
{
64+
name: "success with sync=false & dryRun=true",
65+
request: &model.CreateAppVersionRequest{ApplicationKey: "test-app", Version: "1.0.0"},
66+
sync: false,
67+
dryRun: true,
4568
mockResponse: &http.Response{StatusCode: 202},
4669
mockResponseBody: "{}",
4770
mockError: nil,
@@ -51,6 +74,7 @@ func TestCreateAppVersion(t *testing.T) {
5174
name: "failure",
5275
request: &model.CreateAppVersionRequest{ApplicationKey: "test-app", Version: "1.0.0"},
5376
sync: true,
77+
dryRun: false,
5478
mockResponse: &http.Response{StatusCode: 400},
5579
mockResponseBody: "error",
5680
mockError: nil,
@@ -60,6 +84,7 @@ func TestCreateAppVersion(t *testing.T) {
6084
name: "http client error",
6185
request: &model.CreateAppVersionRequest{ApplicationKey: "test-app", Version: "1.0.0"},
6286
sync: true,
87+
dryRun: false,
6388
mockResponse: nil,
6489
mockResponseBody: "",
6590
mockError: errors.New("http client error"),
@@ -70,13 +95,13 @@ func TestCreateAppVersion(t *testing.T) {
7095
for _, tt := range tests {
7196
t.Run(tt.name, func(t *testing.T) {
7297
mockHttpClient := mockhttp.NewMockApptrustHttpClient(ctrl)
73-
mockHttpClient.EXPECT().Post("/v1/applications/test-app/versions/", tt.request, map[string]string{"async": strconv.FormatBool(!tt.sync)}).
98+
mockHttpClient.EXPECT().Post("/v1/applications/test-app/versions/", tt.request, map[string]string{"async": strconv.FormatBool(!tt.sync), "dry_run": strconv.FormatBool(tt.dryRun)}).
7499
Return(tt.mockResponse, []byte(tt.mockResponseBody), tt.mockError).Times(1)
75100

76101
mockCtx := mockservice.NewMockContext(ctrl)
77102
mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1)
78103

79-
err := service.CreateAppVersion(mockCtx, tt.request, tt.sync)
104+
err := service.CreateAppVersion(mockCtx, tt.request, tt.sync, tt.dryRun)
80105
if tt.expectedError == "" {
81106
assert.NoError(t, err)
82107
} else {
@@ -476,28 +501,6 @@ func TestRollbackAppVersion(t *testing.T) {
476501
expectedStatus: http.StatusBadRequest,
477502
expectedError: true,
478503
},
479-
{
480-
name: "failed rollback - sync=true but got 202",
481-
applicationKey: "video-encoder",
482-
version: "1.5.0",
483-
payload: &model.RollbackAppVersionRequest{
484-
FromStage: "qa",
485-
},
486-
sync: true,
487-
expectedStatus: http.StatusAccepted,
488-
expectedError: true,
489-
},
490-
{
491-
name: "failed rollback - sync=false but got 200",
492-
applicationKey: "video-encoder",
493-
version: "1.5.0",
494-
payload: &model.RollbackAppVersionRequest{
495-
FromStage: "prod",
496-
},
497-
sync: false,
498-
expectedStatus: http.StatusOK,
499-
expectedError: true,
500-
},
501504
}
502505

503506
for _, tt := range tests {

e2e/version_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,38 @@ func TestCreateVersion_ApplicationVersion(t *testing.T) {
9191
assertVersionContent(t, testPackage, versionContent, statusCode, targetAppKey, targetVersion)
9292
}
9393

94+
func TestCreateVersion_ApplicationVersion_DryRun(t *testing.T) {
95+
// Prepare - create source application with a version
96+
sourceAppKey := utils.GenerateUniqueKey("app-version-create-app-version-dryrun")
97+
utils.CreateBasicApplication(t, sourceAppKey)
98+
defer utils.DeleteApplication(t, sourceAppKey)
99+
100+
testPackage := utils.GetTestPackage(t)
101+
sourceVersion := "1.0.2"
102+
packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s",
103+
testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey)
104+
err := utils.AppTrustCli.Exec("version-create", sourceAppKey, sourceVersion, packageFlag)
105+
require.NoError(t, err)
106+
defer utils.DeleteApplicationVersion(t, sourceAppKey, sourceVersion)
107+
108+
// Prepare - create target application
109+
targetAppKey := utils.GenerateUniqueKey("app-target-version-dryrun")
110+
utils.CreateBasicApplication(t, targetAppKey)
111+
defer utils.DeleteApplication(t, targetAppKey)
112+
113+
targetVersion := "1.0.3"
114+
115+
// Execute with dry-run flag
116+
appVersionFlag := fmt.Sprintf("--source-type-application-versions=application-key=%s, version=%s", sourceAppKey, sourceVersion)
117+
err = utils.AppTrustCli.Exec("version-create", targetAppKey, targetVersion, appVersionFlag, "--dry-run")
118+
require.NoError(t, err)
119+
120+
// Assert - version should not exist since it was a dry run
121+
_, statusCode, err := utils.GetApplicationVersion(targetAppKey, targetVersion)
122+
assert.NoError(t, err)
123+
assert.Equal(t, http.StatusNotFound, statusCode)
124+
}
125+
94126
func TestCreateVersion_ReleaseBundle(t *testing.T) {
95127
// Prepare
96128
appKey := utils.GenerateUniqueKey("app-version-create-release-bundle")

0 commit comments

Comments
 (0)