Files
go-storage/s3.go

188 lines
4.4 KiB
Go

package go_storage
import (
"context"
"fmt"
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
// S3Storage implements Storage interface for Amazon S3.
type S3Storage struct {
session *session.Session
bucket string
region string
}
// NewS3Storage creates a new S3 storage instance.
func NewS3Storage(config Config) (*S3Storage, error) {
if err := validateConfig(config, map[string]string{
"bucket": config.S3Bucket,
}); err != nil {
return nil, fmt.Errorf("s3: %w", err)
}
awsConfig := aws.NewConfig()
if config.S3Region != "" {
awsConfig = awsConfig.WithRegion(config.S3Region)
}
if config.S3KeyID != "" && config.S3Secret != "" {
awsConfig = awsConfig.WithCredentials(
credentials.NewStaticCredentials(config.S3KeyID, config.S3Secret, ""),
)
}
if config.S3Endpoint != "" {
awsConfig = awsConfig.WithEndpoint(config.S3Endpoint)
}
if config.S3ForcePath {
awsConfig = awsConfig.WithS3ForcePathStyle(true)
}
if config.S3DisableTLS {
awsConfig = awsConfig.WithDisableSSL(true)
}
sessionOpts := session.Options{
Config: *awsConfig,
SharedConfigState: session.SharedConfigEnable,
}
if config.S3Profile != "" {
sessionOpts.Profile = config.S3Profile
}
sess, err := session.NewSessionWithOptions(sessionOpts)
if err != nil {
return nil, fmt.Errorf("s3: failed to create session: %w", err)
}
return &S3Storage{
session: sess,
bucket: config.S3Bucket,
region: config.S3Region,
}, nil
}
// Upload uploads a file to S3.
func (s *S3Storage) Upload(ctx context.Context, localPath string, remotePath string) error {
file, err := os.Open(localPath)
if err != nil {
return fmt.Errorf("s3: failed to open local file: %w", err)
}
defer func(file *os.File) {
err = file.Close()
if err != nil {
fmt.Printf("s3: warning: failed to close file: %v\n", err)
}
}(file)
uploader := s3manager.NewUploader(s.session)
_, err = uploader.UploadWithContext(ctx, &s3manager.UploadInput{
Bucket: aws.String(s.bucket),
Key: aws.String(normalizePathSeparators(remotePath)),
Body: file,
})
if err != nil {
return fmt.Errorf("s3: failed to upload to bucket %s: %w", s.bucket, err)
}
return nil
}
// Download downloads a file from S3.
func (s *S3Storage) Download(ctx context.Context, remotePath string, localPath string) error {
file, err := os.Create(localPath)
if err != nil {
return fmt.Errorf("s3: failed to create local file: %w", err)
}
defer func(file *os.File) {
err = file.Close()
if err != nil {
fmt.Printf("s3: warning: failed to close file: %v\n", err)
}
}(file)
downloader := s3manager.NewDownloader(s.session)
_, err = downloader.DownloadWithContext(ctx, file, &s3.GetObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(normalizePathSeparators(remotePath)),
})
if err != nil {
return fmt.Errorf("s3: failed to download from bucket %s: %w", s.bucket, err)
}
return nil
}
// List lists objects in S3 with the given prefix.
func (s *S3Storage) List(ctx context.Context, prefix string) ([]Item, error) {
svc := s3.New(s.session)
items := make([]Item, 0)
var continuationToken *string
for {
input := &s3.ListObjectsV2Input{
Bucket: aws.String(s.bucket),
Prefix: aws.String(normalizePathSeparators(prefix)),
ContinuationToken: continuationToken,
}
resp, err := svc.ListObjectsV2WithContext(ctx, input)
if err != nil {
return nil, fmt.Errorf("s3: failed to list objects in bucket %s: %w", s.bucket, err)
}
for _, obj := range resp.Contents {
items = append(items, Item{
Key: *obj.Key,
ModifiedTime: *obj.LastModified,
Size: *obj.Size,
IsDirectory: false,
})
}
if resp.IsTruncated == nil || !*resp.IsTruncated {
break
}
continuationToken = resp.NextContinuationToken
}
return items, nil
}
// Remove deletes an object from S3.
func (s *S3Storage) Remove(ctx context.Context, remotePath string) error {
svc := s3.New(s.session)
_, err := svc.DeleteObjectWithContext(ctx, &s3.DeleteObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(normalizePathSeparators(remotePath)),
})
if err != nil {
return fmt.Errorf("s3: failed to remove object from bucket %s: %w", s.bucket, err)
}
return nil
}
// Close releases resources.
func (s *S3Storage) Close() error {
return nil
}