Skip to content
Open
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
3 changes: 3 additions & 0 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (
DeletePropertiesFlag = "delete-properties"
IncludeFilterFlag = "include-filter"
ExcludeFilterFlag = "exclude-filter"
ConflictResolutionFlag = "conflict-resolution"
PathMappingFlag = "path-mapping"
)

Expand Down Expand Up @@ -106,6 +107,7 @@ var flagsMap = map[string]components.Flag{
SourceTypeArtifactsFlag: components.NewStringFlag(SourceTypeArtifactsFlag, "List of semicolon-separated (;) artifacts in the form of 'path=repo/path/to/artifact1[, sha256=hash1]; path=repo/path/to/artifact2[, sha256=hash2]' to be included in the new version.", func(f *components.StringFlag) { f.Mandatory = false }),
PropertiesFlag: components.NewStringFlag(PropertiesFlag, "Sets or updates custom properties for the application version in format 'key1=value1[,value2,...];key2=value3[,value4,...]'", func(f *components.StringFlag) { f.Mandatory = false }),
DeletePropertiesFlag: components.NewStringFlag(DeletePropertiesFlag, "Remove a property key and all its values", func(f *components.StringFlag) { f.Mandatory = false }),
ConflictResolutionFlag: components.NewStringFlag(ConflictResolutionFlag, "How to resolve source conflicts when the same artifact path appears in multiple sources. Supported values: 'automatic' (pick the newest artifact by creation date, this is the default in Artifactory), 'manual' (reject the request if conflicts exist; use --include-filter / --exclude-filter to resolve).", func(f *components.StringFlag) { f.Mandatory = false }),
PathMappingFlag: components.NewStringFlag(PathMappingFlag, "List of semicolon-separated (;) path mapping rules in the form of 'input=(.*), output=stable-release/$1[, package-type=.*]; input=(.*\\.jar), output=jars/$1, package-type=maven'. Note: quote the value to prevent shell expansion of $1.", func(f *components.StringFlag) { f.Mandatory = false }),
}

Expand All @@ -119,6 +121,7 @@ var commandFlags = map[string][]string{
TagFlag,
DraftFlag,
SkipUnassignedFlag,
ConflictResolutionFlag,
SourceTypeBuildsFlag,
SourceTypeReleaseBundlesFlag,
SourceTypeApplicationVersionsFlag,
Expand Down
17 changes: 10 additions & 7 deletions apptrust/commands/version/create_app_version_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import (
)

type createAppVersionCommand struct {
versionService versions.VersionService
serverDetails *coreConfig.ServerDetails
requestPayload *model.CreateAppVersionRequest
sync bool
dryRun bool
responseBody []byte
versionService versions.VersionService
serverDetails *coreConfig.ServerDetails
requestPayload *model.CreateAppVersionRequest
sync bool
dryRun bool
conflictResolution string
responseBody []byte
}

func (cv *createAppVersionCommand) Run() error {
Expand All @@ -34,7 +35,7 @@ func (cv *createAppVersionCommand) Run() error {
return err
}

cv.responseBody, err = cv.versionService.CreateAppVersion(ctx, cv.requestPayload, cv.sync, cv.dryRun)
cv.responseBody, err = cv.versionService.CreateAppVersion(ctx, cv.requestPayload, cv.sync, cv.dryRun, cv.conflictResolution)
return err
}

Expand All @@ -61,6 +62,7 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context)
return err
}
cv.dryRun = ctx.GetBoolFlagValue(commands.DryRunFlag)
cv.conflictResolution = ctx.GetStringFlagValue(commands.ConflictResolutionFlag)

outputFormat, err := ctx.GetOutputFormat()
if err != nil {
Expand Down Expand Up @@ -122,6 +124,7 @@ Common patterns:
$ jf apptrust version-create my-app 1.0.0 --source-type-packages="type=docker, name=my-image, version=1.0.0, repo-key=docker-local"
$ jf apptrust version-create my-app 1.0.0 --spec=version-spec.json --spec-vars="BUILD=42"
$ jf apptrust version-create my-app 1.0.0 --source-type-builds="name=b, id=1" --draft --dry-run
$ jf apptrust version-create my-app 1.0.0 --source-type-builds="name=b, id=1" --conflict-resolution=automatic

Gotchas:
- The version should follow SemVer convention (e.g. 1.0.0, 1.2.3-rc1); the CLI does not validate the format, but the platform may reject non-conforming values.
Expand Down
16 changes: 8 additions & 8 deletions apptrust/commands/version/create_app_version_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ func TestCreateAppVersionCommand(t *testing.T) {

mockVersionService := mockversions.NewMockVersionService(ctrl)
if tt.shouldError {
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun).
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun, "").
Return(nil, errors.New(tt.errorMessage)).Times(1)
} else {
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun).
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), tt.request, true, tt.dryRun, "").
Return(nil, nil).Times(1)
}

Expand Down Expand Up @@ -234,8 +234,8 @@ func TestCreateAppVersionCommand_FlagsSuite(t *testing.T) {
var actualPayload *model.CreateAppVersionRequest
mockVersionService := mockversions.NewMockVersionService(ctrl)
if !tt.expectsError {
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, _ bool, _ bool) ([]byte, error) {
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, _ bool, _ bool, _ string) ([]byte, error) {
actualPayload = req
return nil, nil
}).Times(1)
Expand Down Expand Up @@ -845,8 +845,8 @@ func TestCreateAppVersionCommand_SpecFileSuite(t *testing.T) {
var capturedSync bool
mockVersionService := mockversions.NewMockVersionService(ctrl)
if !tt.expectsError {
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) {
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool, _ string) ([]byte, error) {
actualPayload = req
capturedSync = sync
return nil, nil
Expand Down Expand Up @@ -914,8 +914,8 @@ func TestCreateAppVersionCommand_SyncFlagSuite(t *testing.T) {

var capturedSync bool
mockVersionService := mockversions.NewMockVersionService(ctrl)
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) {
mockVersionService.EXPECT().CreateAppVersion(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
DoAndReturn(func(_ interface{}, req *model.CreateAppVersionRequest, sync, dryRun bool, _ string) ([]byte, error) {
capturedSync = sync
return nil, nil
}).Times(1)
Expand Down
8 changes: 4 additions & 4 deletions apptrust/service/versions/mocks/version_service_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions apptrust/service/versions/version_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

type VersionService interface {
CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error)
CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool, conflictResolution string) ([]byte, error)
PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) ([]byte, error)
ReleaseAppVersion(ctx service.Context, applicationKey string, version string, request *model.ReleaseAppVersionRequest, sync bool) ([]byte, error)
RollbackAppVersion(ctx service.Context, applicationKey string, version string, request *model.RollbackAppVersionRequest, sync bool) ([]byte, error)
Expand All @@ -30,10 +30,16 @@ func NewVersionService() VersionService {
return &versionService{}
}

func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool) ([]byte, error) {
func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest, sync, dryRun bool, conflictResolution string) ([]byte, error) {
endpoint := fmt.Sprintf("/v1/applications/%s/versions/", request.ApplicationKey)
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request,
map[string]string{"async": strconv.FormatBool(!sync), "dry_run": strconv.FormatBool(dryRun)})
params := map[string]string{
"async": strconv.FormatBool(!sync),
"dry_run": strconv.FormatBool(dryRun),
}
if conflictResolution != "" {
params["conflict_resolution"] = conflictResolution
}
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, params)
if err != nil {
return nil, err
}
Expand Down
46 changes: 45 additions & 1 deletion apptrust/service/versions/version_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestCreateAppVersion(t *testing.T) {
mockCtx := mockservice.NewMockContext(ctrl)
mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1)

_, err := service.CreateAppVersion(mockCtx, tt.request, tt.sync, tt.dryRun)
_, err := service.CreateAppVersion(mockCtx, tt.request, tt.sync, tt.dryRun, "")
if tt.expectedError == "" {
assert.NoError(t, err)
} else {
Expand All @@ -112,6 +112,50 @@ func TestCreateAppVersion(t *testing.T) {
}
}

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

service := NewVersionService()

tests := []struct {
name string
conflictResolution string
expectedParams map[string]string
}{
{
name: "automatic",
conflictResolution: "automatic",
expectedParams: map[string]string{"async": "false", "dry_run": "false", "conflict_resolution": "automatic"},
},
{
name: "manual",
conflictResolution: "manual",
expectedParams: map[string]string{"async": "false", "dry_run": "false", "conflict_resolution": "manual"},
},
{
name: "empty (omitted)",
conflictResolution: "",
expectedParams: map[string]string{"async": "false", "dry_run": "false"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
request := &model.CreateAppVersionRequest{ApplicationKey: "test-app", Version: "1.0.0"}
mockHttpClient := mockhttp.NewMockApptrustHttpClient(ctrl)
mockHttpClient.EXPECT().Post("/v1/applications/test-app/versions/", request, tt.expectedParams).
Return(&http.Response{StatusCode: 201}, []byte("{}"), nil).Times(1)

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

_, err := service.CreateAppVersion(mockCtx, request, true, false, tt.conflictResolution)
assert.NoError(t, err)
})
}
}

func TestPromoteAppVersion(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down
56 changes: 56 additions & 0 deletions e2e/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,62 @@ func TestCreateVersion_Async(t *testing.T) {
assert.Contains(t, []string{utils.StatusInProgress, utils.StatusStarted}, response.Status)
}

func TestCreateVersion_ConflictResolution_Automatic(t *testing.T) {
t.Skip("Skipping: conflict_resolution support requires a platform version not yet released")
appKey := utils.GenerateUniqueKey("app-cr-auto")
utils.CreateBasicApplication(t, appKey)
defer utils.DeleteApplication(t, appKey)

testPackage := utils.GetTestPackage(t)
version := "1.0.0"

packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s",
testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey)
err := utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag, "--conflict-resolution=automatic")
require.NoError(t, err)
defer utils.DeleteApplicationVersion(t, appKey, version)

versionContent, statusCode, err := utils.GetApplicationVersion(appKey, version)
require.NoError(t, err)
assertVersionContent(t, testPackage, versionContent, statusCode, appKey, version)
}

func TestCreateVersion_ConflictResolution_Manual(t *testing.T) {
t.Skip("Skipping: conflict_resolution support requires a platform version not yet released")
appKey := utils.GenerateUniqueKey("app-cr-manual")
utils.CreateBasicApplication(t, appKey)
defer utils.DeleteApplication(t, appKey)

testPackage := utils.GetTestPackage(t)
version := "1.0.0"

packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s",
testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey)
err := utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag, "--conflict-resolution=manual")
require.NoError(t, err)
defer utils.DeleteApplicationVersion(t, appKey, version)

versionContent, statusCode, err := utils.GetApplicationVersion(appKey, version)
require.NoError(t, err)
assertVersionContent(t, testPackage, versionContent, statusCode, appKey, version)
}

func TestCreateVersion_ConflictResolution_Invalid(t *testing.T) {
t.Skip("Skipping: conflict_resolution support requires a platform version not yet released")
appKey := utils.GenerateUniqueKey("app-cr-invalid")
utils.CreateBasicApplication(t, appKey)
defer utils.DeleteApplication(t, appKey)

testPackage := utils.GetTestPackage(t)
version := "1.0.0"

packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s",
testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey)
err := utils.AppTrustCli.Exec("version-create", appKey, version, packageFlag, "--conflict-resolution=bogus")
assert.Error(t, err)
assert.Contains(t, err.Error(), "400")
}

func assertVersionContent(t *testing.T, expectedPackage *utils.TestPackageResources, versionContent *utils.VersionContentResponse, statusCode int, appKey, appVersion string) {
assert.Equal(t, http.StatusOK, statusCode)
require.NotNil(t, versionContent)
Expand Down
Loading