Skip to content

Commit

Permalink
Merge pull request #27 from wolfeidau/feat_add_write_file
Browse files Browse the repository at this point in the history
feat(write_file): added write file extension for simple put object
  • Loading branch information
wolfeidau authored Jan 17, 2024
2 parents b8cc889 + 861930d commit 57b7228
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The `s3File` implements the following interfaces:
In addition to this the `S3FS` also implements the following interfaces:

- `RemoveFS`, which provides a `Remove(name string) error` method.
- `WriteFileFS` which provides a `WriteFile(name string, data []byte, perm fs.FileMode) error` method.

This enables libraries such as [apache arrow](https://arrow.apache.org/) to read parts of a parquet file from S3, without downloading the entire file.
# Usage
Expand Down
25 changes: 25 additions & 0 deletions integration/s3fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,28 @@ func TestRemove(t *testing.T) {
assert.Error(err)
})
}

func TestWriteFile(t *testing.T) {

t.Run("should write and read file", func(t *testing.T) {
assert := require.New(t)

s3fs := s3iofs.NewWithClient(testBucketName, client)

err := s3fs.WriteFile("test_write_read.txt", oneKilobyte, 0644)
assert.NoError(err)

data, err := fs.ReadFile(s3fs, "test_write_read.txt")
assert.NoError(err)
assert.Equal(oneKilobyte, data)
})

t.Run("invalid name should error", func(t *testing.T) {
assert := require.New(t)

s3fs := s3iofs.NewWithClient(testBucketName, client)

err := s3fs.WriteFile("", []byte{}, 0644)
assert.Error(err)
})
}
1 change: 1 addition & 0 deletions s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ type S3API interface {
ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error)
HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error)
DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error)
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
}
5 changes: 5 additions & 0 deletions s3file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ func (m *mockS3Client) DeleteObject(ctx context.Context, params *s3.DeleteObject
return args.Get(0).(*s3.DeleteObjectOutput), args.Error(1)
}

func (m *mockS3Client) PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) {
args := m.Called(ctx, params, optFns)
return args.Get(0).(*s3.PutObjectOutput), args.Error(1)
}

func TestReadFile(t *testing.T) {
type args struct {
bucket string
Expand Down
37 changes: 37 additions & 0 deletions s3iofs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package s3iofs

import (
"bytes"
"errors"
"io/fs"
"os"
Expand All @@ -17,6 +18,7 @@ var (
_ fs.StatFS = (*S3FS)(nil)
_ fs.ReadDirFS = (*S3FS)(nil)
_ RemoveFS = (*S3FS)(nil)
_ WriteFileFS = (*S3FS)(nil)
)

// RemoveFS extend the fs.FS interface to add the Remove method.
Expand All @@ -25,6 +27,12 @@ type RemoveFS interface {
Remove(name string) error
}

// WriteFileFS extend the fs.FS interface to add the WriteFile method.
type WriteFileFS interface {
fs.FS
WriteFile(name string, data []byte, perm os.FileMode) error
}

// S3FS is a filesystem implementation using S3.
type S3FS struct {
bucket string
Expand Down Expand Up @@ -162,6 +170,9 @@ func (s3fs *S3FS) ReadDir(name string) ([]fs.DirEntry, error) {
return entries, nil
}

// Remove removes the named file or directory.
//
// Note if the file doesn't exist in the s3 bucket, Remove returns nil.
func (s3fs *S3FS) Remove(name string) error {
if name == "." {
return &fs.PathError{Op: "remove", Path: name, Err: fs.ErrInvalid}
Expand All @@ -181,6 +192,32 @@ func (s3fs *S3FS) Remove(name string) error {
return nil
}

// WriteFile writes the data to the named file in s3.
//
// Note:
// - If the file exists, WriteFile overwrites it.
// - The provided mode is unused by this implementation.
func (s3fs *S3FS) WriteFile(name string, data []byte, perm os.FileMode) error {
if name == "." {
return &fs.PathError{Op: "write", Path: name, Err: fs.ErrInvalid}
}
if name == "" {
return &fs.PathError{Op: "write", Path: name, Err: fs.ErrInvalid}
}

_, err := s3fs.s3client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(s3fs.bucket),
Key: aws.String(name),
Body: bytes.NewReader(data),
})

if err != nil {
return &fs.PathError{Op: "write", Path: name, Err: err}
}

return nil
}

func (s3fs *S3FS) stat(name string) (fs.FileInfo, error) {
if name == "." {
return &s3File{
Expand Down

0 comments on commit 57b7228

Please sign in to comment.