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
127 changes: 127 additions & 0 deletions artifactory/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
curldocs "github.com/jfrog/jfrog-cli-artifactory/artifactory/docs/curl"
"github.com/jfrog/jfrog-cli-artifactory/artifactory/docs/delete"
"github.com/jfrog/jfrog-cli-artifactory/artifactory/docs/deleteprops"
"github.com/jfrog/jfrog-cli-artifactory/artifactory/docs/directdownload"
"github.com/jfrog/jfrog-cli-artifactory/artifactory/docs/dockerpromote"
"github.com/jfrog/jfrog-cli-artifactory/artifactory/docs/dockerpull"
"github.com/jfrog/jfrog-cli-artifactory/artifactory/docs/dockerpush"
Expand Down Expand Up @@ -102,6 +103,15 @@ func GetCommands() []components.Command {
Action: downloadCmd,
Category: filesCategory,
},
{
Name: "direct-download",
Flags: flagkit.GetCommandFlags(flagkit.DirectDownload),
Aliases: []string{"ddl"},
Description: directdownload.GetDescription(),
Arguments: directdownload.GetArguments(),
Action: directDownloadCmd,
Category: filesCategory,
},
{
Name: "move",
Flags: flagkit.GetCommandFlags(flagkit.Move),
Expand Down Expand Up @@ -665,6 +675,123 @@ func prepareDownloadCommand(c *components.Context) (*spec.SpecFiles, error) {
return downloadSpec, nil
}

func prepareDirectDownloadCommand(c *components.Context) (*spec.SpecFiles, error) {
if c.GetNumberOfArgs() > 0 && c.IsFlagSet("spec") {
return nil, common.PrintHelpAndReturnError("No arguments should be sent when the spec option is used.", c)
}
if !(c.GetNumberOfArgs() == 1 || c.GetNumberOfArgs() == 2 || (c.GetNumberOfArgs() == 0 && (c.IsFlagSet("spec") || c.IsFlagSet("build")))) {
return nil, common.PrintHelpAndReturnError("Wrong number of arguments. Expected: <source-pattern> [target-path] OR --spec=<spec-file> OR --build=<build-name>/<build-number>", c)
}

var (
downloadSpec *spec.SpecFiles
err error
)

if c.IsFlagSet("spec") {
downloadSpec, err = commonCliUtils.GetSpec(c, true, true)
} else {
downloadSpec = createDirectDownloadSpec(c)
}

if err != nil {
return nil, err
}

setTransitiveInDownloadSpec(downloadSpec)
err = spec.ValidateSpec(downloadSpec.Files, false, true)
if err != nil {
return nil, err
}
return downloadSpec, nil
}

func createDirectDownloadSpec(c *components.Context) *spec.SpecFiles {
excludeArtifactsString := c.GetStringFlagValue("exclude-artifacts")
excludeArtifacts, err := parseStringToBool(excludeArtifactsString)
if err != nil {
log.Warn("Could not parse exclude-artifacts flag. Setting exclude-artifacts as false, error: ", err.Error())
}

includeDepsString := c.GetStringFlagValue("include-deps")
includeDeps, err := parseStringToBool(includeDepsString)
if err != nil {
log.Warn("Could not parse include-deps flag. Setting include-deps as false, error: ", err.Error())
}

return spec.NewBuilder().
Pattern(getSourcePattern(c)).
Build(c.GetStringFlagValue("build")).
Bundle(c.GetStringFlagValue("bundle")).
ExcludeArtifacts(excludeArtifacts).
IncludeDeps(includeDeps).
Recursive(c.GetBoolTFlagValue("recursive")).
Exclusions(c.GetStringsArrFlagValue("exclusions")).
Flat(c.GetBoolFlagValue("flat")).
Explode(strconv.FormatBool(c.GetBoolFlagValue("explode"))).
Target(c.GetArgumentAt(1)).
BuildSpec()
}

func parseStringToBool(value string) (bool, error) {
if value == "" {
return false, nil
}

boolValue, err := strconv.ParseBool(value)
if err != nil {
return false, err
}

return boolValue, nil
}

func directDownloadCmd(c *components.Context) error {
downloadSpec, err := prepareDirectDownloadCommand(c)
if err != nil {
return err
}
fixWinPathsForDownloadCmd(downloadSpec, c)
configuration, err := artifactoryUtils.CreateDownloadConfiguration(c)
if err != nil {
return err
}
serverDetails, err := common.CreateArtifactoryDetailsByFlags(c)
if err != nil {
return err
}
buildConfiguration, err := common.CreateBuildConfigurationWithModule(c)
if err != nil {
return err
}
retries, err := getRetries(c)
if err != nil {
return err
}
retryWaitTime, err := getRetryWaitTime(c)
if err != nil {
return err
}

directDownloadCommand := generic.NewDirectDownloadCommand()
directDownloadCommand.SetConfiguration(configuration).SetBuildConfiguration(buildConfiguration).SetSpec(downloadSpec).SetServerDetails(serverDetails).SetDryRun(c.GetBoolFlagValue("dry-run")).SetSyncDeletesPath(c.GetStringFlagValue("sync-deletes")).SetQuiet(common.GetQuietValue(c)).SetDetailedSummary(c.GetBoolFlagValue("detailed-summary")).SetRetries(retries).SetRetryWaitMilliSecs(retryWaitTime)

if directDownloadCommand.ShouldPrompt() && !coreutils.AskYesNo("Sync-deletes may delete some files in your local file system. Are you sure you want to continue?\n"+
"You can avoid this confirmation message by adding --quiet to the command.", false) {
return nil
}

err = progressbar.ExecWithProgress(directDownloadCommand)
result := directDownloadCommand.Result()
defer common.CleanupResult(result, &err)
basicSummary, err := common.CreateSummaryReportString(result.SuccessCount(), result.FailCount(), common.IsFailNoOp(c), err)
if err != nil {
return err
}
err = common.PrintDetailedSummaryReport(basicSummary, result.Reader(), false, err)
return common.GetCliError(err, result.SuccessCount(), result.FailCount(), common.IsFailNoOp(c))
}

func downloadCmd(c *components.Context) error {
downloadSpec, err := prepareDownloadCommand(c)
if err != nil {
Expand Down
224 changes: 224 additions & 0 deletions artifactory/commands/generic/directdownload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package generic

import (
"errors"
"os"
"path/filepath"
"strconv"

buildinfo "github.com/jfrog/build-info-go/entities"
gofrog "github.com/jfrog/gofrog/io"
"github.com/jfrog/jfrog-cli-core/v2/common/spec"

"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
"github.com/jfrog/jfrog-cli-core/v2/common/build"
"github.com/jfrog/jfrog-client-go/artifactory/services"
serviceutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
"github.com/jfrog/jfrog-client-go/utils/log"
)

type DirectDownloadCommand struct {
DownloadCommand
}

func NewDirectDownloadCommand() *DirectDownloadCommand {
return &DirectDownloadCommand{DownloadCommand: *NewDownloadCommand()}
}

func (ddc *DirectDownloadCommand) CommandName() string {
return "rt_direct_download"
}

func (ddc *DirectDownloadCommand) Run() error {
return ddc.directDownload()
}

func (ddc *DirectDownloadCommand) directDownload() error {
// Init progress bar if needed
if ddc.progress != nil {
ddc.progress.SetHeadlineMsg("")
ddc.progress.InitProgressReaders()
}

servicesManager, err := utils.CreateDownloadServiceManager(ddc.serverDetails, ddc.configuration.Threads, ddc.retries, ddc.retryWaitTimeMilliSecs, ddc.DryRun(), ddc.progress)
if err != nil {
return err
}

// Build Info Collection:
toCollect, err := ddc.buildConfiguration.IsCollectBuildInfo()
if err != nil {
return err
}
if toCollect && !ddc.DryRun() {
var buildName, buildNumber string
buildName, err = ddc.buildConfiguration.GetBuildName()
if err != nil {
return err
}
buildNumber, err = ddc.buildConfiguration.GetBuildNumber()
if err != nil {
return err
}
if err = build.SaveBuildGeneralDetails(buildName, buildNumber, ddc.buildConfiguration.GetProject()); err != nil {
return err
}
}

// var downloadParamsArray []services.DirectDownloadParams
var errorOccurred = false
var downloadParamsArray []services.DirectDownloadParams
// Create DownloadParams for all File-Spec groups.
var downParams services.DirectDownloadParams
for i := 0; i < len(ddc.Spec().Files); i++ {
downParams, err = getDirectDownloadParams(ddc.Spec().Get(i), ddc.configuration)
if err != nil {
errorOccurred = true
log.Error(err)
continue
}
downloadParamsArray = append(downloadParamsArray, downParams)
}
// Perform download.
// In case of build-info collection/sync-deletes operation/a detailed summary is required, we use the download service which provides results file reader,
// otherwise we use the download service which provides only general counters.
var totalDownloaded, totalFailed int
var summary *serviceutils.OperationSummary
if toCollect || ddc.SyncDeletesPath() != "" || ddc.DetailedSummary() {
summary, err = servicesManager.DirectDownloadFilesWithSummary(downloadParamsArray...)
if err != nil {
errorOccurred = true
log.Error(err)
}
if summary != nil {
defer gofrog.Close(summary.ArtifactsDetailsReader, &err)
// If 'detailed summary' was requested, then the reader should not be closed here.
// It will be closed after it will be used to generate the summary.
if ddc.DetailedSummary() {
ddc.result.SetReader(summary.TransferDetailsReader)
} else {
defer gofrog.Close(summary.TransferDetailsReader, &err)
}
totalDownloaded = summary.TotalSucceeded
totalFailed = summary.TotalFailed
}
} else {
totalDownloaded, totalFailed, err = servicesManager.DirectDownloadFiles(downloadParamsArray...)
if err != nil {
errorOccurred = true
log.Error(err)
}
}
ddc.result.SetSuccessCount(totalDownloaded)
ddc.result.SetFailCount(totalFailed)
// Check for errors.
if errorOccurred {
return errors.New("download finished with errors, please review the logs")
}
if ddc.DryRun() {
ddc.result.SetSuccessCount(totalDownloaded)
ddc.result.SetFailCount(0)
return err
} else if ddc.SyncDeletesPath() != "" {
var absSyncDeletesPath string
absSyncDeletesPath, err = filepath.Abs(ddc.SyncDeletesPath())
if err != nil {
return errorutils.CheckError(err)
}
if _, err = os.Stat(absSyncDeletesPath); err == nil {
// Unmarshal the local paths of the downloaded files from the results file reader
var tmpRoot string
tmpRoot, err = createDownloadResultEmptyTmpReflection(summary.TransferDetailsReader)
defer func() {
err = errors.Join(err, fileutils.RemoveTempDir(tmpRoot))
}()
if err != nil {
return err
}
walkFn := createSyncDeletesWalkFunction(tmpRoot)
err = gofrog.Walk(ddc.SyncDeletesPath(), walkFn, false)
if err != nil {
return errorutils.CheckError(err)
}
} else if os.IsNotExist(err) {
log.Info("Sync-deletes path", absSyncDeletesPath, "does not exists.")
}
}
log.Debug("Downloaded", strconv.Itoa(totalDownloaded), "artifacts.")

// Build Info
if toCollect {
var buildName, buildNumber string
buildName, err = ddc.buildConfiguration.GetBuildName()
if err != nil {
return err
}
buildNumber, err = ddc.buildConfiguration.GetBuildNumber()
if err != nil {
return err
}
var buildDependencies []buildinfo.Dependency
buildDependencies, err = serviceutils.ConvertArtifactsDetailsToBuildInfoDependencies(summary.ArtifactsDetailsReader)
if err != nil {
return err
}
populateFunc := func(partial *buildinfo.Partial) {
partial.Dependencies = buildDependencies
partial.ModuleId = ddc.buildConfiguration.GetModule()
partial.ModuleType = buildinfo.Generic
}
return build.SavePartialBuildInfo(buildName, buildNumber, ddc.buildConfiguration.GetProject(), populateFunc)
}

return err
}

func getDirectDownloadParams(f *spec.File, configuration *utils.DownloadConfiguration) (downParams services.DirectDownloadParams, err error) {
downParams = services.NewDirectDownloadParams()
downParams.CommonParams, err = f.ToCommonParams()
if err != nil {
return
}
downParams.MinSplitSize = configuration.MinSplitSize
downParams.SplitCount = configuration.SplitCount
downParams.SkipChecksum = configuration.SkipChecksum

downParams.Recursive, err = f.IsRecursive(true)
if err != nil {
return
}

downParams.IncludeDirs, err = f.IsIncludeDirs(false)
if err != nil {
return
}

downParams.Flat, err = f.IsFlat(false)
if err != nil {
return
}

downParams.Explode, err = f.IsExplode(false)
if err != nil {
return
}

downParams.ExcludeArtifacts, err = f.IsExcludeArtifacts(false)
if err != nil {
return
}

downParams.IncludeDeps, err = f.IsIncludeDeps(false)
if err != nil {
return
}

downParams.Transitive, err = f.IsTransitive(false)
if err != nil {
return
}

return
}
Loading
Loading