Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support uploading files with Size filtering and AQL avoiding #963

Merged
merged 70 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
2f8216c
Fix npm script
sverdlov93 Jun 2, 2024
ab2fe46
Merge branch 'dev' of https://github.com/jfrog/jfrog-client-go into c…
sverdlov93 Jun 2, 2024
ddb9dc8
Fix npm script
sverdlov93 Jun 16, 2024
23ced3c
Fix npm script
sverdlov93 Jun 16, 2024
08f66e6
Merge branch 'dev' into clean-proj
sverdlov93 Jun 16, 2024
966083a
Fix npm script
sverdlov93 Jun 16, 2024
431f511
Merge remote-tracking branch 'sverdlov93/clean-proj' into clean-proj
sverdlov93 Jun 16, 2024
1477208
Fix npm script
sverdlov93 Jun 24, 2024
32719c0
Fix npm script
sverdlov93 Jun 24, 2024
5235076
Fix npm script
sverdlov93 Jun 24, 2024
adfaa7d
Fix npm script
sverdlov93 Jun 24, 2024
eac1741
Merge branch 'dev' of https://github.com/jfrog/jfrog-client-go into c…
sverdlov93 Jun 26, 2024
648bbc4
Fix npm script
sverdlov93 Jun 27, 2024
6a51851
Fix npm script
sverdlov93 Jul 3, 2024
b8ca905
Fix npm script
sverdlov93 Jul 3, 2024
8b3356e
Fix npm script
sverdlov93 Jul 4, 2024
d9f3c12
Fix npm script
sverdlov93 Jul 4, 2024
fb373b0
Fix npm script
sverdlov93 Jul 4, 2024
156bc44
Merge branch 'dev' into clean-proj
sverdlov93 Jul 7, 2024
af215d7
progress bar updates
omerzi Jul 7, 2024
c979cff
Merge remote-tracking branch 'refs/remotes/origin/clean-proj' into cl…
omerzi Jul 7, 2024
5a9a708
fix balagan
omerzi Jul 7, 2024
9f5b064
fix archive
omerzi Jul 7, 2024
d04e851
zip panic fix
omerzi Jul 7, 2024
55784a5
fix race
omerzi Jul 7, 2024
5007d43
Fix npm script
sverdlov93 Jul 7, 2024
d95b9af
Fix npm script
sverdlov93 Jul 7, 2024
49958e3
Fix npm script
sverdlov93 Jul 7, 2024
d39a3f4
fix balagan
omerzi Jul 7, 2024
4ddef17
fix race
omerzi Jul 7, 2024
01ee745
Merge branch 'dev' of https://github.com/jfrog/jfrog-client-go into c…
sverdlov93 Jul 7, 2024
7719b0f
Merge remote-tracking branch 'sverdlov93/clean-proj' into clean-proj
sverdlov93 Jul 7, 2024
879e387
Fix npm script
sverdlov93 Jul 11, 2024
61a5d00
Fix npm script
sverdlov93 Jul 14, 2024
9429362
Fix npm script
sverdlov93 Jul 14, 2024
0fa6d98
Merge branch 'dev' of https://github.com/jfrog/jfrog-client-go into c…
sverdlov93 Jul 14, 2024
b8f7e8c
Fix npm script
sverdlov93 Jul 14, 2024
a00a64f
Fix npm script
sverdlov93 Jul 14, 2024
7587801
Fix npm script
sverdlov93 Jul 14, 2024
bab495d
Fix npm script
sverdlov93 Jul 14, 2024
fd44618
Fix npm script
sverdlov93 Jul 14, 2024
2285387
Fix npm script
sverdlov93 Jul 14, 2024
9004a68
Merge branch 'dev' of https://github.com/jfrog/jfrog-client-go into c…
sverdlov93 Jul 14, 2024
3a07f25
Fix npm script
sverdlov93 Jul 14, 2024
09880b7
Fix npm script
sverdlov93 Jul 14, 2024
650cf6c
Fix npm script
sverdlov93 Jul 14, 2024
b48b013
Fix npm script
sverdlov93 Jul 14, 2024
b021efd
Fix npm script
sverdlov93 Jul 14, 2024
860c4c5
Fix npm script
sverdlov93 Jul 14, 2024
c87f5d8
Fix npm script
sverdlov93 Jul 14, 2024
7df2a13
Fix npm script
sverdlov93 Jul 14, 2024
b737137
Fix npm script
sverdlov93 Jul 14, 2024
3319870
Fix npm script
sverdlov93 Jul 15, 2024
f3f26b2
Fix npm script
sverdlov93 Jul 15, 2024
545f022
Fix npm script
sverdlov93 Jul 15, 2024
2836df3
Fix npm script
sverdlov93 Jul 15, 2024
f5a21c0
Fix npm script
sverdlov93 Jul 15, 2024
eac9ad2
Fix npm script
sverdlov93 Jul 15, 2024
c56b7d2
Fix npm script
sverdlov93 Jul 15, 2024
2416c2b
Fix npm script
sverdlov93 Jul 15, 2024
cdd0d58
Fix npm script
sverdlov93 Jul 15, 2024
10d06b9
Fix npm script
sverdlov93 Jul 15, 2024
73c4ce1
Fix npm script
sverdlov93 Jul 15, 2024
88416a6
Fix npm script
sverdlov93 Jul 15, 2024
79b6da3
Merge branch 'dev' into clean-proj
sverdlov93 Jul 15, 2024
482f005
Fix npm script
sverdlov93 Jul 15, 2024
8762ae5
Fix npm script
sverdlov93 Jul 15, 2024
a47dfd7
Fix npm script
sverdlov93 Jul 15, 2024
0a4142a
Fix npm script
sverdlov93 Jul 15, 2024
11c7f99
Fix npm script
sverdlov93 Jul 15, 2024
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
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
- [Distribute Release Bundle](#distribute-release-bundle)
- [Delete Release Bundle Version](#delete-release-bundle-version)
- [Delete Release Bundle Version Promotion](#delete-release-bundle-version-promotion)
- [Export Release Bundle](#export-release-bundle)
- [Export Release Bundle Archive](#export-release-bundle-archive)
- [Import Release Bundle](#import-release-bundle)
- [Remote Delete Release Bundle](#remote-delete-release-bundle)
- [Lifecycle APIs](#lifecycle-apis)
Expand Down Expand Up @@ -455,7 +455,10 @@ params.ChecksumsCalcEnabled = false
targetProps := utils.NewProperties()
targetProps.AddProperty("key1", "val1")
params.TargetProps = targetProps

// When using the 'archive' option for upload, we can control the target path inside the uploaded archive using placeholders. This operation determines the TargetPathInArchive value.
TargetPathInArchive := "archive/path/"
// Size limit for files to be uploaded.
SizeLimit= &fspatterns.SizeThreshold{SizeInBytes: 10000, Condition: fspatterns.LessThan}
totalUploaded, totalFailed, err := rtManager.UploadFiles(params)
```

Expand Down Expand Up @@ -484,7 +487,9 @@ params.Retries = 5
params.SplitCount = 2
// MinSplitSize default value: 5120
params.MinSplitSize = 7168

// Optional fields to avoid AQL request
Sha256 = "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9"
Size = 1000
totalDownloaded, totalFailed, err := rtManager.DownloadFiles(params)
```

Expand Down
92 changes: 75 additions & 17 deletions artifactory/services/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package services

import (
"errors"
"github.com/jfrog/build-info-go/entities"
biutils "github.com/jfrog/build-info-go/utils"
ioutils "github.com/jfrog/gofrog/io"
"github.com/jfrog/gofrog/version"
"net/http"
"os"
"path"
"path/filepath"
"sort"

biutils "github.com/jfrog/build-info-go/utils"
"github.com/jfrog/gofrog/version"

"github.com/jfrog/build-info-go/entities"
"strings"

"github.com/jfrog/jfrog-client-go/http/httpclient"

Expand Down Expand Up @@ -164,17 +164,30 @@ func (ds *DownloadService) prepareTasks(producer parallel.Runner, expectedChan c
errorsQueue.AddError(err)
}
}

var reader *content.ContentReader
// Create handler function for the current group.
fileHandlerFunc := ds.createFileHandlerFunc(downloadParams, successCounters)
// Search items.
switch downloadParams.GetSpecType() {
case utils.WILDCARD:
reader, err = ds.collectFilesUsingWildcardPattern(downloadParams)
case utils.BUILD:
reader, err = utils.SearchBySpecWithBuild(downloadParams.GetFile(), ds)
case utils.AQL:
reader, err = utils.SearchBySpecWithAql(downloadParams.GetFile(), ds, utils.SYMLINK)
// Check if we can avoid using AQL to get the file's info.
avoidAql, err := isFieldsProvidedToAvoidAql(downloadParams)
// Check for search errors.
if err != nil {
log.Error(err)
errorsQueue.AddError(err)
continue
}
if avoidAql {
reader, err = createResultsItemWithoutAql(downloadParams)
sverdlov93 marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Search items using AQL and get their details (size/checksum/etc.) from Artifactory.
switch downloadParams.GetSpecType() {
case utils.WILDCARD:
reader, err = utils.SearchBySpecWithPattern(downloadParams.GetFile(), ds, utils.SYMLINK)
case utils.BUILD:
reader, err = utils.SearchBySpecWithBuild(downloadParams.GetFile(), ds)
case utils.AQL:
reader, err = utils.SearchBySpecWithAql(downloadParams.GetFile(), ds, utils.SYMLINK)
}
}
// Check for search errors.
if err != nil {
Expand All @@ -196,8 +209,49 @@ func (ds *DownloadService) prepareTasks(producer parallel.Runner, expectedChan c
}()
}

func (ds *DownloadService) collectFilesUsingWildcardPattern(downloadParams DownloadParams) (*content.ContentReader, error) {
return utils.SearchBySpecWithPattern(downloadParams.GetFile(), ds, utils.SYMLINK)
func isFieldsProvidedToAvoidAql(downloadParams DownloadParams) (bool, error) {
if downloadParams.Sha256 != "" && downloadParams.Size != nil {
// If sha256 and size is provided, we can avoid using AQL to get the file's info.
return true, nil
} else if downloadParams.Sha256 == "" && downloadParams.Size == nil {
// If sha256 and size is missing, we can't avoid using AQL to get the file's info.
return false, nil
}
// If only one of the fields is provided, return an error.
return false, errors.New("both sha256 and size must be provided in order to avoid using AQL")
}

func createResultsItemWithoutAql(downloadParams DownloadParams) (*content.ContentReader, error) {
writer, err := content.NewContentWriter(content.DefaultKey, true, false)
if err != nil {
return nil, err
}
defer ioutils.Close(writer, &err)
repo, path, name, err := breakFileDownloadPathToParts(downloadParams.GetPattern())
if err != nil {
return nil, err
}
resultItem := &utils.ResultItem{
Type: string(utils.File),
Repo: repo,
Path: path,
Name: name,
Size: *downloadParams.Size,
Sha256: downloadParams.Sha256,
}
writer.Write(*resultItem)
return content.NewContentReader(writer.GetFilePath(), writer.GetArrayKey()), nil
}

func breakFileDownloadPathToParts(downloadPath string) (repo, path, name string, err error) {
if utils.IsWildcardPattern(downloadPath) {
return "", "", "", errorutils.CheckErrorf("downloading without AQL is not supported for the provided wildcard pattern: " + downloadPath)
}
parts := strings.Split(downloadPath, "/")
repo = parts[0]
path = strings.Join(parts[1:len(parts)-1], "/")
name = parts[len(parts)-1]
return
}

func (ds *DownloadService) produceTasks(reader *content.ContentReader, downloadParams DownloadParams, producer parallel.Runner, fileHandler fileHandlerFunc, errorsQueue *clientutils.ErrorsQueue) int {
Expand Down Expand Up @@ -399,6 +453,7 @@ func (ds *DownloadService) downloadFile(downloadFileDetails *httpclient.Download
LocalFileName: downloadFileDetails.LocalFileName,
LocalPath: downloadFileDetails.LocalPath,
ExpectedSha1: downloadFileDetails.ExpectedSha1,
ExpectedSha256: downloadFileDetails.ExpectedSha256,
FileSize: downloadFileDetails.Size,
SplitCount: downloadParams.SplitCount,
Explode: downloadParams.Explode,
Expand Down Expand Up @@ -515,8 +570,8 @@ func (ds *DownloadService) createFileHandlerFunc(downloadParams DownloadParams,
return err
}
if downloadParams.IsSymlink() {
if isSymlink, e := ds.createSymlinkIfNeeded(ds.GetArtifactoryDetails().GetUrl(), localPath, localFileName, logMsgPrefix, downloadData, successCounters, threadId, downloadParams); isSymlink {
return e
if isSymlink, err := ds.createSymlinkIfNeeded(ds.GetArtifactoryDetails().GetUrl(), localPath, localFileName, logMsgPrefix, downloadData, successCounters, threadId, downloadParams); isSymlink {
return err
}
}
if err = ds.downloadFileIfNeeded(downloadPath, localPath, localFileName, logMsgPrefix, downloadData, downloadParams); err != nil {
Expand Down Expand Up @@ -591,6 +646,9 @@ type DownloadParams struct {
SplitCount int
PublicGpgKey string
SkipChecksum bool
// Optional fields to avoid AQL request
Sha256 string
Size *int64
}

func (ds *DownloadParams) IsFlat() bool {
Expand Down
38 changes: 38 additions & 0 deletions artifactory/services/download_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package services

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestBreakFileDownloadPathToParts(t *testing.T) {
testCases := []struct {
name string
downloadPath string
expectedRepo string
expectedPath string
expectedName string
expectError bool
}{
{"Single level path", "repo/file.txt", "repo", "", "file.txt", false},
{"Multi-level path", "repo/folder/subfolder/file.txt", "repo", "folder/subfolder", "file.txt", false},
{"Root level file", "repo/", "", "", "", true},
{"Empty path", "", "", "", "", true},
{"Invalid path", "file.txt", "", "", "", true},
{"Wildcard path", "repo/*.txt", "", "", "", true},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
repo, path, name, err := breakFileDownloadPathToParts(tt.downloadPath)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedRepo, repo)
assert.Equal(t, tt.expectedPath, path)
assert.Equal(t, tt.expectedName, name)
})
}
}
27 changes: 27 additions & 0 deletions artifactory/services/fspatterns/sizethreshold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fspatterns
sverdlov93 marked this conversation as resolved.
Show resolved Hide resolved

// ThresholdCondition represents whether the threshold is for files above or below a specified size.
type ThresholdCondition int

const (
// GreaterThan is greater & equal
GreaterEqualThan ThresholdCondition = iota
// LessThan is only less
LessThan
)

type SizeThreshold struct {
SizeInBytes int64
Condition ThresholdCondition
}

func (st SizeThreshold) IsSizeWithinThreshold(actualSizeInBytes int64) bool {
switch st.Condition {
case GreaterEqualThan:
return actualSizeInBytes >= st.SizeInBytes
case LessThan:
return actualSizeInBytes < st.SizeInBytes
default:
return false
}
}
53 changes: 53 additions & 0 deletions artifactory/services/fspatterns/sizethreshold_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package fspatterns

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestSizeWithinLimits(t *testing.T) {
tests := []struct {
name string
st SizeThreshold
actualSize int64
expectedResult bool
}{
{
name: "Exact size as threshold and condition is GreaterEqualThan returns true",
st: SizeThreshold{SizeInBytes: 100, Condition: GreaterEqualThan},
actualSize: 100,
expectedResult: true,
},
{
name: "SizeInBytes above threshold and condition is GreaterEqualThan returns true",
st: SizeThreshold{SizeInBytes: 100, Condition: GreaterEqualThan},
actualSize: 150,
expectedResult: true,
},
{
name: "SizeInBytes below threshold and condition is GreaterEqualThan returns false",
st: SizeThreshold{SizeInBytes: 100, Condition: GreaterEqualThan},
actualSize: 50,
expectedResult: false,
},
{
name: "Exact size as threshold and condition is LessThan returns false",
st: SizeThreshold{SizeInBytes: 100, Condition: LessThan},
actualSize: 100,
expectedResult: false,
},
{
name: "SizeInBytes above threshold and condition is LessThan returns false",
st: SizeThreshold{SizeInBytes: 100, Condition: LessThan},
actualSize: 150,
expectedResult: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.st.IsSizeWithinThreshold(tt.actualSize)
assert.Equal(t, tt.expectedResult, result)
})
}
}
Loading
Loading