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
17 changes: 15 additions & 2 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ const (
UserOwnersFlag = "user-owners"
GroupOwnersFlag = "group-owners"
SigningKeyFlag = "signing-key"
SyncFlag = "sync"
PromotionTypeFlag = "promotion-type"
DryRunFlag = "dry-run"
ExcludeReposFlag = "exclude-repos"
IncludeReposFlag = "include-repos"
)

// Flag keys mapped to their corresponding components.Flag definition.
Expand Down Expand Up @@ -69,6 +74,11 @@ var flagsMap = map[string]components.Flag{
UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "Comma-separated list of user owners.", func(f *components.StringFlag) { f.Mandatory = false }),
GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "Comma-separated list of group owners.", func(f *components.StringFlag) { f.Mandatory = false }),
SigningKeyFlag: components.NewStringFlag(SigningKeyFlag, "The GPG/RSA key-pair name given in Artifactory.", func(f *components.StringFlag) { f.Mandatory = false }),
SyncFlag: components.NewBoolFlag(SyncFlag, "Whether to synchronize the operation.", components.WithBoolDefaultValueTrue()),
PromotionTypeFlag: components.NewStringFlag(PromotionTypeFlag, "The promotion type. The following values are supported: "+coreutils.ListToText(model.PromotionTypeValues), func(f *components.StringFlag) { f.Mandatory = false; f.DefaultValue = model.PromotionTypeCopy }),
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 }),
}

var commandFlags = map[string][]string{
Expand All @@ -90,8 +100,11 @@ var commandFlags = map[string][]string{
user,
accessToken,
serverId,
ApplicationKeyFlag,
StageVarsFlag,
SyncFlag,
PromotionTypeFlag,
DryRunFlag,
ExcludeReposFlag,
IncludeReposFlag,
},
DeleteAppVersion: {
url,
Expand Down
62 changes: 55 additions & 7 deletions apptrust/commands/version/promote_app_version_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import (
type promoteAppVersionCommand struct {
versionService versions.VersionService
serverDetails *coreConfig.ServerDetails
applicationKey string
version string
requestPayload *model.PromoteAppVersionRequest
sync bool
}

func (pv *promoteAppVersionCommand) Run() error {
Expand All @@ -29,7 +32,7 @@ func (pv *promoteAppVersionCommand) Run() error {
return err
}

return pv.versionService.PromoteAppVersion(ctx, pv.requestPayload)
return pv.versionService.PromoteAppVersion(ctx, pv.applicationKey, pv.version, pv.requestPayload, pv.sync)
}

func (pv *promoteAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) {
Expand All @@ -41,9 +44,17 @@ func (pv *promoteAppVersionCommand) CommandName() string {
}

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

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

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

serverDetails, err := utils.ServerDetailsByFlags(ctx)
if err != nil {
return err
Expand All @@ -57,10 +68,37 @@ 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)
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
}

return &model.PromoteAppVersionRequest{
ApplicationKey: ctx.GetStringFlagValue(commands.ApplicationKeyFlag),
Version: ctx.Arguments[0],
Environment: ctx.GetStringFlagValue(commands.StageVarsFlag),
Stage: stage,
PromotionType: validatedPromotionType,
IncludedRepositoryKeys: includedRepos,
ExcludedRepositoryKeys: excludedRepos,
}, nil
}

Expand All @@ -73,8 +111,18 @@ func GetPromoteAppVersionCommand(appContext app.Context) components.Command {
Aliases: []string{"vp"},
Arguments: []components.Argument{
{
Name: "version-name",
Description: "The name of the version",
Name: "application-key",
Description: "The application key",
Optional: false,
},
{
Name: "version",
Description: "The version to promote",
Optional: false,
},
{
Name: "target-stage",
Description: "The target stage to which the application version should be promoted",
Optional: false,
},
},
Expand Down
77 changes: 65 additions & 12 deletions apptrust/commands/version/promote_app_version_cmd_test.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,100 @@
package version

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"

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

func TestRun(t *testing.T) {
func TestPromoteAppVersionCommand_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.PromoteAppVersionRequest{
Stage: "prod",
}

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

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

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

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

serverDetails := &coreconfig.ServerDetails{}
serverDetails := &config.ServerDetails{Url: "https://example.com"}
applicationKey := "app-key"
version := "1.0.0"
requestPayload := &model.PromoteAppVersionRequest{
ApplicationKey: "app",
Version: "1.0.0",
Environment: "env",
Stage: "prod",
}
sync := true
expectedError := errors.New("service error occurred")

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

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

err := cmd.Run()
assert.NoError(t, err)
assert.Error(t, err)
assert.Contains(t, err.Error(), "service error occurred")
}

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

serverDetails := &coreconfig.ServerDetails{}
serverDetails := &config.ServerDetails{}
cmd := &promoteAppVersionCommand{
serverDetails: serverDetails,
}
Expand All @@ -51,7 +104,7 @@ func TestServerDetails(t *testing.T) {
assert.Equal(t, serverDetails, details)
}

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

Expand Down
6 changes: 3 additions & 3 deletions apptrust/http/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const apptrustApiPath = "apptrust/api"

type ApptrustHttpClient interface {
GetHttpClient() *jfroghttpclient.JfrogHttpClient
Post(path string, requestBody interface{}) (resp *http.Response, body []byte, err error)
Post(path string, requestBody interface{}, params map[string]string) (resp *http.Response, body []byte, err error)
Get(path string) (resp *http.Response, body []byte, err error)
Patch(path string, requestBody interface{}) (resp *http.Response, body []byte, err error)
Delete(path string, requestBody interface{}) (resp *http.Response, body []byte, err error)
Expand Down Expand Up @@ -86,8 +86,8 @@ func (c *apptrustHttpClient) GetHttpClient() *jfroghttpclient.JfrogHttpClient {
return c.client
}

func (c *apptrustHttpClient) Post(path string, requestBody interface{}) (resp *http.Response, body []byte, err error) {
url, err := utils.BuildUrl(c.serverDetails.Url, apptrustApiPath+path, nil)
func (c *apptrustHttpClient) Post(path string, requestBody interface{}, params map[string]string) (resp *http.Response, body []byte, err error) {
url, err := utils.BuildUrl(c.serverDetails.Url, apptrustApiPath+path, params)
if err != nil {
return nil, nil, err
}
Expand Down
21 changes: 18 additions & 3 deletions apptrust/model/promote_app_version_request.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
package model

const (
PromotionTypeCopy = "copy"
PromotionTypeMove = "move"

// This value cannot be set via the --promotion-type flag in the CLI.
// It is sent to the promotion_type field in the REST API only when the --dry-run flag is used.
PromotionTypeDryRun = "dry_run"
)

var PromotionTypeValues = []string{
PromotionTypeCopy,
PromotionTypeMove,
}

type PromoteAppVersionRequest struct {
ApplicationKey string `json:"application_key"`
Version string `json:"version"`
Environment string `json:"environment"`
Stage string `json:"stage"`
PromotionType string `json:"promotion_type,omitempty"`
IncludedRepositoryKeys []string `json:"included_repository_keys,omitempty"`
ExcludedRepositoryKeys []string `json:"excluded_repository_keys,omitempty"`
}
2 changes: 1 addition & 1 deletion apptrust/service/applications/application_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewApplicationService() ApplicationService {
}

func (as *applicationService) CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) error {
response, responseBody, err := ctx.GetHttpClient().Post("/v1/applications", requestBody)
response, responseBody, err := ctx.GetHttpClient().Post("/v1/applications", requestBody, nil)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion apptrust/service/applications/application_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestApplicationService_CreateApplication(t *testing.T) {
defer ctrl.Finish()

mockHttpClient := mockhttp.NewMockApptrustHttpClient(ctrl)
mockHttpClient.EXPECT().Post("/v1/applications", gomock.Any()).Return(tt.mockResponse, tt.mockBody, tt.mockError)
mockHttpClient.EXPECT().Post("/v1/applications", gomock.Any(), nil).Return(tt.mockResponse, tt.mockBody, tt.mockError)

mockCtx := mockservice.NewMockContext(ctrl)
mockCtx.EXPECT().GetHttpClient().Return(mockHttpClient).Times(1)
Expand Down
2 changes: 1 addition & 1 deletion apptrust/service/packages/package_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func NewPackageService() PackageService {

func (ps *packageService) BindPackage(ctx service.Context, request *model.BindPackageRequest) error {
endpoint := "/v1/package"
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request)
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, nil)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion apptrust/service/packages/package_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestBindPackage(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockHttpClient := mockhttp.NewMockApptrustHttpClient(ctrl)
mockHttpClient.EXPECT().Post("/v1/package", tt.request).
mockHttpClient.EXPECT().Post("/v1/package", tt.request, nil).
Return(tt.mockResponse, []byte(""), tt.mockError).Times(1)

mockCtx := mockservice.NewMockContext(ctrl)
Expand Down
12 changes: 7 additions & 5 deletions apptrust/service/versions/version_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package versions

import (
"fmt"
"strconv"

"github.com/jfrog/jfrog-cli-application/apptrust/service"

Expand All @@ -12,7 +13,7 @@ import (

type VersionService interface {
CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest) error
PromoteAppVersion(ctx service.Context, payload *model.PromoteAppVersionRequest) error
PromoteAppVersion(ctx service.Context, applicationKey string, version string, payload *model.PromoteAppVersionRequest, sync bool) error
DeleteAppVersion(ctx service.Context, applicationKey string, version string) error
}

Expand All @@ -23,7 +24,7 @@ func NewVersionService() VersionService {
}

func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.CreateAppVersionRequest) error {
response, responseBody, err := ctx.GetHttpClient().Post("/v1/applications/version", request)
response, responseBody, err := ctx.GetHttpClient().Post("/v1/applications/version", request, nil)
if err != nil {
return err
}
Expand All @@ -36,8 +37,9 @@ func (vs *versionService) CreateAppVersion(ctx service.Context, request *model.C
return nil
}

func (vs *versionService) PromoteAppVersion(ctx service.Context, payload *model.PromoteAppVersionRequest) error {
response, responseBody, err := ctx.GetHttpClient().Post("/v1/applications/version/promote", payload)
func (vs *versionService) PromoteAppVersion(ctx service.Context, applicationKey, version string, request *model.PromoteAppVersionRequest, sync bool) error {
endpoint := fmt.Sprintf("/v1/applications/%s/versions/%s/promote", applicationKey, version)
response, responseBody, err := ctx.GetHttpClient().Post(endpoint, request, map[string]string{"async": strconv.FormatBool(!sync)})
if err != nil {
return err
}
Expand All @@ -50,7 +52,7 @@ func (vs *versionService) PromoteAppVersion(ctx service.Context, payload *model.
return nil
}

func (vs *versionService) DeleteAppVersion(ctx service.Context, applicationKey string, version string) error {
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, nil)
if err != nil {
Expand Down
Loading
Loading