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
15 changes: 15 additions & 0 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
VersionCreate = "version-create"
VersionPromote = "version-promote"
VersionDelete = "version-delete"
VersionRelease = "version-release"
PackageBind = "package-bind"
PackageUnbind = "package-unbind"
AppCreate = "app-create"
Expand Down Expand Up @@ -47,6 +48,7 @@ const (
DryRunFlag = "dry-run"
ExcludeReposFlag = "exclude-repos"
IncludeReposFlag = "include-repos"
PropsFlag = "props"
)

// Flag keys mapped to their corresponding components.Flag definition.
Expand Down Expand Up @@ -79,6 +81,7 @@ var flagsMap = map[string]components.Flag{
DryRunFlag: components.NewBoolFlag(DryRunFlag, "Perform a simulation of the operation.", components.WithBoolDefaultValueFalse()),
ExcludeReposFlag: components.NewStringFlag(ExcludeReposFlag, "Semicolon-separated list of repositories to exclude.", func(f *components.StringFlag) { f.Mandatory = false }),
IncludeReposFlag: components.NewStringFlag(IncludeReposFlag, "Semicolon-separated list of repositories to include.", func(f *components.StringFlag) { f.Mandatory = false }),
PropsFlag: components.NewStringFlag(PropsFlag, "Semicolon-separated list of properties in the form of 'key1=value1;key2=value2;...' to be added to each artifact.", func(f *components.StringFlag) { f.Mandatory = false }),
}

var commandFlags = map[string][]string{
Expand All @@ -105,6 +108,18 @@ var commandFlags = map[string][]string{
DryRunFlag,
ExcludeReposFlag,
IncludeReposFlag,
PropsFlag,
},
VersionRelease: {
url,
user,
accessToken,
serverId,
SyncFlag,
PromotionTypeFlag,
ExcludeReposFlag,
IncludeReposFlag,
PropsFlag,
},
VersionDelete: {
url,
Expand Down
33 changes: 11 additions & 22 deletions apptrust/commands/version/promote_app_version_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,35 +70,24 @@ func (pv *promoteAppVersionCommand) prepareAndRunCommand(ctx *components.Context
func (pv *promoteAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.PromoteAppVersionRequest, error) {
stage := ctx.Arguments[2]

var includedRepos []string
var excludedRepos []string

if includeReposStr := ctx.GetStringFlagValue(commands.IncludeReposFlag); includeReposStr != "" {
includedRepos = utils.ParseSliceFlag(includeReposStr)
}

if excludeReposStr := ctx.GetStringFlagValue(commands.ExcludeReposFlag); excludeReposStr != "" {
excludedRepos = utils.ParseSliceFlag(excludeReposStr)
}

// Validate promotion type flag
promotionType := ctx.GetStringFlagValue(commands.PromotionTypeFlag)
validatedPromotionType, err := utils.ValidateEnumFlag(commands.PromotionTypeFlag, promotionType, model.PromotionTypeCopy, model.PromotionTypeValues)
promotionType, includedRepos, excludedRepos, err := BuildPromotionParams(ctx)
if err != nil {
return nil, err
}

// If dry-run is true, override with dry_run
dryRun := ctx.GetBoolFlagValue(commands.DryRunFlag)
if dryRun {
validatedPromotionType = model.PromotionTypeDryRun
artifactProps, err := ParseArtifactProps(ctx)
if err != nil {
return nil, err
}

return &model.PromoteAppVersionRequest{
Stage: stage,
PromotionType: validatedPromotionType,
IncludedRepositoryKeys: includedRepos,
ExcludedRepositoryKeys: excludedRepos,
Stage: stage,
CommonPromoteAppVersion: model.CommonPromoteAppVersion{
PromotionType: promotionType,
IncludedRepositoryKeys: includedRepos,
ExcludedRepositoryKeys: excludedRepos,
ArtifactAdditionalProperties: artifactProps,
},
}, nil
}

Expand Down
23 changes: 0 additions & 23 deletions apptrust/commands/version/promote_app_version_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"testing"

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

Expand Down Expand Up @@ -89,25 +88,3 @@ func TestPromoteAppVersionCommand_Run_Error(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), "service error occurred")
}

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

serverDetails := &config.ServerDetails{}
cmd := &promoteAppVersionCommand{
serverDetails: serverDetails,
}

details, err := cmd.ServerDetails()
assert.NoError(t, err)
assert.Equal(t, serverDetails, details)
}

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

cmd := &promoteAppVersionCommand{}
assert.Equal(t, commands.VersionPromote, cmd.CommandName())
}
113 changes: 113 additions & 0 deletions apptrust/commands/version/release_app_version_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
)

type releaseAppVersionCommand struct {
versionService versions.VersionService
serverDetails *coreConfig.ServerDetails
applicationKey string
version string
requestPayload *model.ReleaseAppVersionRequest
sync bool
}

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

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

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

func (rv *releaseAppVersionCommand) CommandName() string {
return commands.VersionRelease
}

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

// Extract from arguments
rv.applicationKey = ctx.Arguments[0]
rv.version = ctx.Arguments[1]

// Extract sync flag value
rv.sync = ctx.GetBoolTFlagValue(commands.SyncFlag)

serverDetails, err := utils.ServerDetailsByFlags(ctx)
if err != nil {
return err
}
rv.serverDetails = serverDetails
rv.requestPayload, err = rv.buildRequestPayload(ctx)
if errorutils.CheckError(err) != nil {
return err
}
return commonCLiCommands.Exec(rv)
}

func (rv *releaseAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.ReleaseAppVersionRequest, error) {
promotionType, includedRepos, excludedRepos, err := BuildPromotionParams(ctx)
if err != nil {
return nil, err
}

artifactProps, err := ParseArtifactProps(ctx)
if err != nil {
return nil, err
}

return model.NewReleaseAppVersionRequest(
promotionType,
includedRepos,
excludedRepos,
artifactProps,
), nil
}

func GetReleaseAppVersionCommand(appContext app.Context) components.Command {
cmd := &releaseAppVersionCommand{
versionService: appContext.GetVersionService(),
}
return components.Command{
Name: commands.VersionRelease,
Description: "Release application version.",
Category: common.CategoryVersion,
Aliases: []string{"vr"},
Arguments: []components.Argument{
{
Name: "application-key",
Description: "The application key.",
Optional: false,
},
{
Name: "version",
Description: "The version to release.",
Optional: false,
},
},
Flags: commands.GetCommandFlags(commands.VersionRelease),
Action: cmd.prepareAndRunCommand,
}
}
95 changes: 95 additions & 0 deletions apptrust/commands/version/release_app_version_cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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 TestReleaseAppVersionCommand_Run(t *testing.T) {
tests := []struct {
name string
sync bool
}{
{
name: "sync flag true",
sync: true,
},
{
name: "sync flag false",
sync: false,
},
}

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

serverDetails := &config.ServerDetails{Url: "https://example.com"}
applicationKey := "app-key"
version := "1.0.0"
requestPayload := model.NewReleaseAppVersionRequest(
model.PromotionTypeCopy,
nil, // includedRepos
nil, // excludedRepos
nil, // artifactProps
)

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

cmd := &releaseAppVersionCommand{
versionService: mockVersionService,
serverDetails: serverDetails,
applicationKey: applicationKey,
version: version,
requestPayload: requestPayload,
sync: tt.sync,
}

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

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

serverDetails := &config.ServerDetails{Url: "https://example.com"}
applicationKey := "app-key"
version := "1.0.0"
requestPayload := model.NewReleaseAppVersionRequest(
model.PromotionTypeCopy,
nil, // includedRepos
nil, // excludedRepos
nil, // artifactProps
)
expectedError := errors.New("service error occurred")

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

cmd := &releaseAppVersionCommand{
versionService: mockVersionService,
serverDetails: serverDetails,
applicationKey: applicationKey,
version: version,
requestPayload: requestPayload,
sync: false,
}

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

import (
"github.com/jfrog/jfrog-cli-application/apptrust/commands"
"github.com/jfrog/jfrog-cli-application/apptrust/commands/utils"
"github.com/jfrog/jfrog-cli-application/apptrust/model"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
)

// BuildPromotionParams extracts common promotion parameters from command context
// Used by both promote and release commands
func BuildPromotionParams(ctx *components.Context) (string, []string, []string, error) {
var includedRepos []string
var excludedRepos []string

if includeReposStr := ctx.GetStringFlagValue(commands.IncludeReposFlag); includeReposStr != "" {
includedRepos = utils.ParseSliceFlag(includeReposStr)
}

if excludeReposStr := ctx.GetStringFlagValue(commands.ExcludeReposFlag); excludeReposStr != "" {
excludedRepos = utils.ParseSliceFlag(excludeReposStr)
}

promotionType := ctx.GetStringFlagValue(commands.PromotionTypeFlag)

validatedPromotionType, err := utils.ValidateEnumFlag(commands.PromotionTypeFlag, promotionType, model.PromotionTypeCopy, model.PromotionTypeValues)
if err != nil {
return "", nil, nil, err
}

// If dry-run is true, override with dry_run
dryRun := ctx.GetBoolFlagValue(commands.DryRunFlag)
if dryRun {
validatedPromotionType = model.PromotionTypeDryRun
}

return validatedPromotionType, includedRepos, excludedRepos, nil
}

// ParseArtifactProps extracts artifact properties from command context
func ParseArtifactProps(ctx *components.Context) (map[string]string, error) {
if propsStr := ctx.GetStringFlagValue(commands.PropsFlag); propsStr != "" {
props, err := utils.ParseMapFlag(propsStr)
if err != nil {
return nil, errorutils.CheckErrorf("failed to parse properties: %s", err.Error())
}
return props, nil
}
return nil, nil
}
Loading
Loading