package aws import ( "context" "fmt" "io" "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" ) // S3Config is the subconfig for the S3 storage type type S3Config struct { AccessKeyID string `json:"access_key_id"` SecretAccessKey string `json:"secret_access_key"` Region string `json:"region"` Bucket string `json:"bucket"` Path string `json:"path"` } // S3Client is a client for uploading data to S3. type S3Client struct { region string accessKey string secretKey string bucket string key string // These fields are used for testing via dependency injection. uploader uploader downloader downloader } // NewS3Client returns an instance of an S3Client. func NewS3Client(region, accessKey, secretKey, bucket, key string) *S3Client { return &S3Client{ region: region, accessKey: accessKey, secretKey: secretKey, bucket: bucket, key: key, } } // String returns a string representation of the S3Client. func (s *S3Client) String() string { return fmt.Sprintf("s3://%s/%s", s.bucket, s.key) } // Upload uploads data to S3. func (s *S3Client) Upload(ctx context.Context, reader io.Reader) error { sess, err := s.createSession() if err != nil { return err } // If an uploader was not provided, use a real S3 uploader. var uploader uploader if s.uploader == nil { uploader = s3manager.NewUploader(sess) } else { uploader = s.uploader } _, err = uploader.UploadWithContext(ctx, &s3manager.UploadInput{ Bucket: aws.String(s.bucket), Key: aws.String(s.key), Body: reader, }) if err != nil { return fmt.Errorf("failed to upload to %v: %w", s, err) } return nil } // Download downloads data from S3. func (s *S3Client) Download(ctx context.Context, writer io.WriterAt) error { sess, err := s.createSession() if err != nil { return err } // If a downloader was not provided, use a real S3 downloader. var downloader downloader if s.downloader == nil { downloader = s3manager.NewDownloader(sess) } else { downloader = s.downloader } _, err = downloader.DownloadWithContext(ctx, writer, &s3.GetObjectInput{ Bucket: aws.String(s.bucket), Key: aws.String(s.key), }) if err != nil { return fmt.Errorf("failed to download from %v: %w", s, err) } return nil } func (s *S3Client) createSession() (*session.Session, error) { sess, err := session.NewSession(&aws.Config{ Region: aws.String(s.region), Credentials: credentials.NewStaticCredentials(s.accessKey, s.secretKey, ""), }) if err != nil { return nil, fmt.Errorf("failed to create S3 session: %w", err) } return sess, nil } type uploader interface { UploadWithContext(ctx aws.Context, input *s3manager.UploadInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) } type downloader interface { DownloadWithContext(ctx aws.Context, w io.WriterAt, input *s3.GetObjectInput, opts ...func(*s3manager.Downloader)) (n int64, err error) }