Skip to content

Commit f4548cf

Browse files
Implement multipart upload(AST-24859) (#1343)
* implemented multipart upload * Added wrapper method in mock * fix unit test cases and lint issue * Add test cases * fix unit test case * debug unit test case * debug unit test case * unit test case * unit test case * Remove print msg * handle multipart_file_size value is empty * rename the MultipartFileSizeKey * fix lint issue * Made code chnages according to review comments * remove extra comments --------- Co-authored-by: Anurag Dalke <[email protected]>
1 parent 218a852 commit f4548cf

File tree

10 files changed

+600
-9
lines changed

10 files changed

+600
-9
lines changed

internal/commands/scan.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ var (
153153
aditionalParameters []string
154154
kicsErrorCodes = []string{"60", "50", "40", "30", "20"}
155155
containerResolver wrappers.ContainerResolverWrapper
156+
MaxSizeBytes int64 = 5 * 1024 * 1024 * 1024 // 5 GB in bytes
156157
)
157158

158159
func NewScanCommand(
@@ -2082,7 +2083,20 @@ func uploadZip(uploadsWrapper wrappers.UploadsWrapper, zipFilePath string, unzip
20822083
var zipFilePathErr error
20832084
// Send a request to uploads service
20842085
var preSignedURL *string
2085-
preSignedURL, zipFilePathErr = uploadsWrapper.UploadFile(zipFilePath, featureFlagsWrapper)
2086+
2087+
fileInfo, err := os.Stat(zipFilePath)
2088+
if err != nil {
2089+
return "", zipFilePath, errors.Wrapf(err, "Failed to check the size - %s", zipFilePath)
2090+
}
2091+
logger.PrintIfVerbose(fmt.Sprintf("Zip size before upload: %.2fMB\n", float64(fileInfo.Size())/mbBytes))
2092+
2093+
flagResponse, _ := featureFlagsWrapper.GetSpecificFlag(wrappers.IncreaseFileUploadLimit)
2094+
if flagResponse.Status && fileInfo.Size() > MaxSizeBytes {
2095+
logger.PrintIfVerbose("Uploading source code in multiple parts.")
2096+
preSignedURL, zipFilePathErr = uploadsWrapper.UploadFileInMultipart(zipFilePath, featureFlagsWrapper)
2097+
} else {
2098+
preSignedURL, zipFilePathErr = uploadsWrapper.UploadFile(zipFilePath, featureFlagsWrapper)
2099+
}
20862100
if zipFilePathErr != nil {
20872101
if unzip || !userProvidedZip {
20882102
return "", zipFilePath, errors.Wrapf(zipFilePathErr, "%s: Failed to upload sources file\n", failedCreating)

internal/commands/scan_test.go

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2972,15 +2972,31 @@ func TestResubmitConfig_ProjectDoesNotExist_ReturnedEmptyConfig(t *testing.T) {
29722972
func TestUploadZip_whenUserProvideZip_shouldReturnEmptyZipFilePathInSuccessCase(t *testing.T) {
29732973
uploadWrapper := mock.UploadsMockWrapper{}
29742974
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
2975-
_, zipPath, err := uploadZip(&uploadWrapper, "test.zip", false, true, featureFlagsWrapper)
2975+
_, zipPath, err := uploadZip(&uploadWrapper, "data/sources.zip", false, true, featureFlagsWrapper)
29762976
assert.NilError(t, err)
29772977
assert.Equal(t, zipPath, "")
29782978
}
29792979

29802980
func TestUploadZip_whenUserProvideZip_shouldReturnEmptyZipFilePathInFailureCase(t *testing.T) {
2981+
// Create a temporary zip file
2982+
dir := t.TempDir()
2983+
zipPathTemp := filepath.Join(dir, "failureCase.zip")
2984+
2985+
// Create the zip file
2986+
zipFile, err := os.Create(zipPathTemp)
2987+
if err != nil {
2988+
t.Fatalf("Failed to create zip file: %v", err)
2989+
}
2990+
defer func(zipFile *os.File) {
2991+
err := zipFile.Close()
2992+
if err != nil {
2993+
t.Fatalf("Failed to close zip file: %v", err)
2994+
}
2995+
}(zipFile)
2996+
29812997
uploadWrapper := mock.UploadsMockWrapper{}
29822998
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
2983-
_, zipPath, err := uploadZip(&uploadWrapper, "failureCase.zip", false, true, featureFlagsWrapper)
2999+
_, zipPath, err := uploadZip(&uploadWrapper, zipPathTemp, false, true, featureFlagsWrapper)
29843000
assert.Assert(t, err != nil)
29853001
assert.Assert(t, strings.Contains(err.Error(), "error from UploadFile"), err.Error())
29863002
assert.Equal(t, zipPath, "")
@@ -2989,18 +3005,33 @@ func TestUploadZip_whenUserProvideZip_shouldReturnEmptyZipFilePathInFailureCase(
29893005
func TestUploadZip_whenUserNotProvideZip_shouldReturnZipFilePathInSuccessCase(t *testing.T) {
29903006
uploadWrapper := mock.UploadsMockWrapper{}
29913007
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
2992-
_, zipPath, err := uploadZip(&uploadWrapper, "test.zip", false, false, featureFlagsWrapper)
3008+
_, zipPath, err := uploadZip(&uploadWrapper, "data/sources.zip", false, false, featureFlagsWrapper)
29933009
assert.NilError(t, err)
2994-
assert.Equal(t, zipPath, "test.zip")
3010+
assert.Equal(t, zipPath, "data/sources.zip")
29953011
}
29963012

29973013
func TestUploadZip_whenUserNotProvideZip_shouldReturnZipFilePathInFailureCase(t *testing.T) {
3014+
// Create a temporary zip file
3015+
dir := t.TempDir()
3016+
zipPathTemp := filepath.Join(dir, "failureCase.zip")
3017+
3018+
// Create the zip file
3019+
zipFile, err := os.Create(zipPathTemp)
3020+
if err != nil {
3021+
t.Fatalf("Failed to create zip file: %v", err)
3022+
}
3023+
defer func(zipFile *os.File) {
3024+
err := zipFile.Close()
3025+
if err != nil {
3026+
t.Fatalf("Failed to close zip file: %v", err)
3027+
}
3028+
}(zipFile)
29983029
uploadWrapper := mock.UploadsMockWrapper{}
29993030
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
3000-
_, zipPath, err := uploadZip(&uploadWrapper, "failureCase.zip", false, false, featureFlagsWrapper)
3031+
_, zipPath, err := uploadZip(&uploadWrapper, zipPathTemp, false, false, featureFlagsWrapper)
30013032
assert.Assert(t, err != nil)
30023033
assert.Assert(t, strings.Contains(err.Error(), "error from UploadFile"), err.Error())
3003-
assert.Equal(t, zipPath, "failureCase.zip")
3034+
assert.Equal(t, zipPath, zipPathTemp)
30043035
}
30053036

30063037
func TestAddSastScan_ScanFlags(t *testing.T) {
@@ -4249,3 +4280,96 @@ func TestEnforceLocalResolutionForTarFiles_Integration(t *testing.T) {
42494280
})
42504281
}
42514282
}
4283+
4284+
func TestUploadZip_AsMultipartUpload_when_FF_Enable_ZIP_Exceeds_5GB(t *testing.T) {
4285+
// Simulate a file size > 5GB by setting MaxSizeBytes to less than actual size
4286+
MaxSizeBytes = 1
4287+
defer func() { MaxSizeBytes = 5 * 1024 * 1024 * 1024 }() // Reset after test
4288+
uploadWrapper := mock.UploadsMockWrapper{}
4289+
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.IncreaseFileUploadLimit, Status: true}
4290+
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
4291+
url, zipPath, err := uploadZip(&uploadWrapper, "data/sources.zip", false, true, featureFlagsWrapper)
4292+
assert.NilError(t, err)
4293+
assert.Equal(t, zipPath, "")
4294+
assert.Equal(t, url, "multiPart/path/to/nowhere")
4295+
}
4296+
4297+
func TestUploadZip_AsMultipartUpload_when_FF_Disable_ZIP_Exceeds_5GB(t *testing.T) {
4298+
fileInfo, err := os.Stat("data/sources.zip")
4299+
if err != nil {
4300+
t.Fatalf("Failed to close zip file: %v", err)
4301+
}
4302+
// Simulate a file size > 5GB by setting MaxSizeBytes to less than actual size
4303+
MaxSizeBytes = fileInfo.Size() - 1
4304+
defer func() { MaxSizeBytes = 5 * 1024 * 1024 * 1024 }() // Reset after test
4305+
4306+
uploadWrapper := mock.UploadsMockWrapper{}
4307+
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.IncreaseFileUploadLimit, Status: false}
4308+
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
4309+
url, zipPath, err := uploadZip(&uploadWrapper, "data/sources.zip", false, true, featureFlagsWrapper)
4310+
assert.NilError(t, err)
4311+
assert.Equal(t, zipPath, "")
4312+
assert.Equal(t, url, "singlePart/path/to/nowhere")
4313+
}
4314+
4315+
func TestUploadZip_AsMultipartUpload_when_FF_Enable_ZIP_LessThan_5GB(t *testing.T) {
4316+
uploadWrapper := mock.UploadsMockWrapper{}
4317+
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.IncreaseFileUploadLimit, Status: true}
4318+
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
4319+
url, zipPath, err := uploadZip(&uploadWrapper, "data/sources.zip", false, true, featureFlagsWrapper)
4320+
assert.NilError(t, err)
4321+
assert.Equal(t, zipPath, "")
4322+
assert.Equal(t, url, "singlePart/path/to/nowhere")
4323+
}
4324+
4325+
func TestUploadZip_AsMultipartUpload_when_FF_Disable_ZIP_LessThan_5GB(t *testing.T) {
4326+
uploadWrapper := mock.UploadsMockWrapper{}
4327+
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.IncreaseFileUploadLimit, Status: false}
4328+
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
4329+
url, zipPath, err := uploadZip(&uploadWrapper, "data/sources.zip", false, true, featureFlagsWrapper)
4330+
assert.NilError(t, err)
4331+
assert.Equal(t, zipPath, "")
4332+
assert.Equal(t, url, "singlePart/path/to/nowhere")
4333+
}
4334+
4335+
func TestUploadZip_AsMultipartUpload_when_FF_Enable_ZIP_Exceeds_5GB_Error(t *testing.T) {
4336+
// Create a temporary zip file
4337+
dir := t.TempDir()
4338+
zipPathTemp := filepath.Join(dir, "failureCaseLarge.zip")
4339+
4340+
// Create the zip file
4341+
zipFile, err := os.Create(zipPathTemp)
4342+
if err != nil {
4343+
t.Fatalf("Failed to create zip file: %v", err)
4344+
}
4345+
defer func(zipFile *os.File) {
4346+
err := zipFile.Close()
4347+
if err != nil {
4348+
t.Fatalf("Failed to close zip file: %v", err)
4349+
}
4350+
}(zipFile)
4351+
4352+
// Seek to 5KB + 1 byte
4353+
_, err = zipFile.Seek(5*1024+1, 0) // 5121 bytes
4354+
if err != nil {
4355+
panic("Failed to seek in zip file: " + err.Error())
4356+
}
4357+
4358+
// Write a single byte to allocate space
4359+
_, err = zipFile.Write([]byte{0})
4360+
if err != nil {
4361+
panic("Failed to write to zip file: " + err.Error())
4362+
}
4363+
4364+
// Simulate a file size > 5GB by setting MaxSizeBytes to less than actual size
4365+
MaxSizeBytes = 1
4366+
defer func() { MaxSizeBytes = 5 * 1024 * 1024 * 1024 }() //
4367+
4368+
uploadWrapper := mock.UploadsMockWrapper{}
4369+
mock.Flag = wrappers.FeatureFlagResponseModel{Name: wrappers.IncreaseFileUploadLimit, Status: true}
4370+
featureFlagsWrapper := &mock.FeatureFlagsMockWrapper{}
4371+
_, zipPath, err := uploadZip(&uploadWrapper, zipPathTemp, false, true, featureFlagsWrapper)
4372+
assert.Assert(t, err != nil)
4373+
assert.Assert(t, strings.Contains(err.Error(), "error from UploadFileInMultipart"), err.Error())
4374+
assert.Equal(t, zipPath, "")
4375+
}

internal/params/binds.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,8 @@ var EnvVarsBinds = []struct {
8080
{RiskManagementPathKey, RiskManagementPathEnv, "api/risk-management/projects/%s/results?scanID=%s"},
8181
{ConfigFilePathKey, ConfigFilePathEnv, ""},
8282
{RealtimeScannerPathKey, RealtimeScannerPathEnv, "api/realtime-scanner"},
83+
{StartMultiPartUploadPathKey, StartMultiPartUploadPathEnv, "api/uploads/start-multipart-upload"},
84+
{MultipartPresignedPathKey, MultipartPresignedPathEnv, "api/uploads/multipart-presigned"},
85+
{CompleteMultiPartUploadPathKey, CompleteMultipartUploadPathEnv, "api/uploads/complete-multipart-upload"},
86+
{MultipartFileSizeKey, MultipartFileSizeEnv, "2"},
8387
}

internal/params/envs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,8 @@ const (
8080
RiskManagementPathEnv = "CX_RISK_MANAGEMENT_PATH"
8181
ConfigFilePathEnv = "CX_CONFIG_FILE_PATH"
8282
RealtimeScannerPathEnv = "CX_REALTIME_SCANNER_PATH"
83+
StartMultiPartUploadPathEnv = "CX_START_MULTIPART_UPLOAD_PATH"
84+
MultipartPresignedPathEnv = "CX_MULTIPART_PRESIGNED_URL_PATH"
85+
CompleteMultipartUploadPathEnv = "CX_COMPLETE_MULTIPART_UPLOAD_PATH"
86+
MultipartFileSizeEnv = "MULTIPART_FILE_SIZE"
8387
)

internal/params/keys.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,8 @@ var (
7979
RiskManagementPathKey = strings.ToLower(RiskManagementPathEnv)
8080
ConfigFilePathKey = strings.ToLower(ConfigFilePathEnv)
8181
RealtimeScannerPathKey = strings.ToLower(RealtimeScannerPathEnv)
82+
StartMultiPartUploadPathKey = strings.ToLower(StartMultiPartUploadPathEnv)
83+
MultipartPresignedPathKey = strings.ToLower(MultipartPresignedPathEnv)
84+
CompleteMultiPartUploadPathKey = strings.ToLower(CompleteMultipartUploadPathEnv)
85+
MultipartFileSizeKey = strings.ToLower(MultipartFileSizeEnv)
8286
)

internal/wrappers/feature-flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const OssRealtimeEnabled = "OSS_REALTIME_ENABLED"
1717
const ScsLicensingV2Enabled = "SSCS_NEW_LICENSING_ENABLED"
1818
const DirectAssociationEnabled = "DIRECT_APP_ASSOCIATION_ENABLED"
1919
const maxRetries = 3
20+
const IncreaseFileUploadLimit = "INCREASE_FILE_UPLOAD_LIMIT"
2021

2122
var DefaultFFLoad bool = false
2223

internal/wrappers/mock/uploads-mock.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mock
22

33
import (
44
"fmt"
5+
"strings"
56

67
"github.com/pkg/errors"
78

@@ -11,11 +12,20 @@ import (
1112
type UploadsMockWrapper struct {
1213
}
1314

15+
func (u *UploadsMockWrapper) UploadFileInMultipart(filePath string, wrapper wrappers.FeatureFlagsWrapper) (*string, error) {
16+
fmt.Println("UploadFileInMultipart called Create in UploadsMockWrapper")
17+
if strings.Contains(filePath, "failureCaseLarge.zip") {
18+
return nil, errors.New("error from UploadFileInMultipart")
19+
}
20+
url := "multiPart/path/to/nowhere"
21+
return &url, nil
22+
}
23+
1424
func (u *UploadsMockWrapper) UploadFile(filePath string, featureFlagsWrapper wrappers.FeatureFlagsWrapper) (*string, error) {
1525
fmt.Println("Called Create in UploadsMockWrapper")
16-
if filePath == "failureCase.zip" {
26+
if strings.Contains(filePath, "failureCase.zip") {
1727
return nil, errors.New("error from UploadFile")
1828
}
19-
url := "/path/to/nowhere"
29+
url := "singlePart/path/to/nowhere"
2030
return &url, nil
2131
}

0 commit comments

Comments
 (0)