From 0b59a4f28483d40d227a92b439274bc55ebc8f2f Mon Sep 17 00:00:00 2001 From: John Judd Date: Mon, 22 Jan 2024 13:14:46 -0600 Subject: [PATCH] Fixed #152 bug where s3 backend failed to read empty files (#153) * Fixed #152 bug where s3 backend failed to read empty files * corrected changelog version --- CHANGELOG.md | 4 ++++ backend/s3/file.go | 40 +++++++++++++++++++++++++--------------- backend/s3/file_test.go | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9cb6a08..bbe20ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [6.11.1] - 2024-01-22 +### Fixed +- Fixed #152 bug where s3 backend failed to read empty files + ## [6.11.0] - 2024-01-22 ### Added - Added support for hmac-sha1 and hmac-sha1-96 and removed hmac-ripemd160 diff --git a/backend/s3/file.go b/backend/s3/file.go index 26308706..fa377d9a 100644 --- a/backend/s3/file.go +++ b/backend/s3/file.go @@ -7,6 +7,7 @@ import ( "io" "net/url" "path" + "strings" "time" "github.com/aws/aws-sdk-go/aws/awserr" @@ -535,26 +536,35 @@ func waitUntilFileExists(file vfs.File, retries int) error { func (f *File) getReader() (io.ReadCloser, error) { if f.reader == nil { - // Create the request to get the object - input := new(s3.GetObjectInput). - SetBucket(f.bucket). - SetKey(f.key). - SetRange(fmt.Sprintf("bytes=%d-", f.cursorPos)) - - // Get the client - client, err := f.fileSystem.Client() + sz, err := f.Size() if err != nil { return nil, err } + if sz == 0 { + f.reader = io.NopCloser(strings.NewReader("")) + } else { - // Request the object - result, err := client.GetObject(input) - if err != nil { - return nil, err - } + // Create the request to get the object + input := new(s3.GetObjectInput). + SetBucket(f.bucket). + SetKey(f.key). + SetRange(fmt.Sprintf("bytes=%d-", f.cursorPos)) - // Set the reader to the body of the object - f.reader = result.Body + // Get the client + client, err := f.fileSystem.Client() + if err != nil { + return nil, err + } + + // Request the object + result, err := client.GetObject(input) + if err != nil { + return nil, err + } + + // Set the reader to the body of the object + f.reader = result.Body + } } return f.reader, nil } diff --git a/backend/s3/file_test.go b/backend/s3/file_test.go index 914c7ddd..b491f9ec 100644 --- a/backend/s3/file_test.go +++ b/backend/s3/file_test.go @@ -63,6 +63,10 @@ func (ts *fileTestSuite) TestRead() { } var localFile = bytes.NewBuffer([]byte{}) + s3apiMock. + On("HeadObject", mock.AnythingOfType("*s3.HeadObjectInput")). + Return(&s3.HeadObjectOutput{ContentLength: aws.Int64(12)}, nil). + Once() s3apiMock. On("GetObject", mock.AnythingOfType("*s3.GetObjectInput")). Return(&s3.GetObjectOutput{Body: io.NopCloser(strings.NewReader(contents))}, nil). @@ -74,6 +78,10 @@ func (ts *fileTestSuite) TestRead() { ts.Equal(contents, localFile.String(), "Copying an s3 file to a buffer should fill buffer with file's contents") // test read with error + s3apiMock. + On("HeadObject", mock.AnythingOfType("*s3.HeadObjectInput")). + Return(&s3.HeadObjectOutput{ContentLength: aws.Int64(12)}, nil). + Once() s3apiMock. On("GetObject", mock.AnythingOfType("*s3.GetObjectInput")). Return(nil, errors.New("some error")). @@ -135,6 +143,10 @@ func (ts *fileTestSuite) TestSeek() { ts.Equal(tc.expectedPos, pos, "Expected position does not match for seek offset %d and whence %d", tc.seekOffset, tc.seekWhence) // Mock the GetObject call + s3apiMock. + On("HeadObject", mock.AnythingOfType("*s3.HeadObjectInput")). + Return(headOutput, nil). + Once() s3apiMock.On("GetObject", mock.AnythingOfType("*s3.GetObjectInput")). Return(&s3.GetObjectOutput{Body: io.NopCloser(strings.NewReader(tc.readContent))}, nil). Once() @@ -235,6 +247,10 @@ func (ts *fileTestSuite) TestEmptyCopyToFile() { targetFile := &mocks.File{} targetFile.On("Write", mock.Anything).Return(0, nil) targetFile.On("Close").Return(nil) + s3apiMock. + On("HeadObject", mock.AnythingOfType("*s3.HeadObjectInput")). + Return(&s3.HeadObjectOutput{ContentLength: aws.Int64(0)}, nil). + Once() s3apiMock. On("GetObject", mock.AnythingOfType("*s3.GetObjectInput")). Return(&s3.GetObjectOutput{Body: io.NopCloser(strings.NewReader(""))}, nil).