Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141package storage
import ( "context" "errors" "fmt" "io" "strings"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types")
type S3Client interface { PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error)}
type S3 struct { bucket string prefix string client S3Client}
func NewS3(client S3Client, bucket, prefix string) (*S3, error) { if client == nil { return nil, fmt.Errorf("storage: s3 backend requires a client") } if bucket == "" { return nil, fmt.Errorf("storage: s3 backend requires a bucket") } return &S3{ bucket: bucket, prefix: strings.Trim(prefix, "/"), client: client, }, nil}
func (s *S3) fullKey(key string) (string, error) { if err := ValidateKey(key); err != nil { return "", err } if s.prefix == "" { return key, nil } return s.prefix + "/" + key, nil}
func (s *S3) Get(ctx context.Context, key string) (io.ReadCloser, error) { full, err := s.fullKey(key) if err != nil { return nil, err } res, err := s.client.GetObject(ctx, &s3.GetObjectInput{ Bucket: &s.bucket, Key: &full, }) if err != nil { var nsk *types.NoSuchKey if errors.As(err, &nsk) { return nil, ErrNotExist } return nil, fmt.Errorf("storage: get %q: %w", key, err) } return res.Body, nil}
func (s *S3) Put(ctx context.Context, key string, r io.Reader) error { full, err := s.fullKey(key) if err != nil { return err } if _, err := s.client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &s.bucket, Key: &full, Body: r, }); err != nil { return fmt.Errorf("storage: put %q: %w", key, err) } return nil}
type s3HeadClient interface { HeadObject(context.Context, *s3.HeadObjectInput, ...func(*s3.Options)) (*s3.HeadObjectOutput, error)}
func (s *S3) Stat(ctx context.Context, key string) (int64, error) { full, err := s.fullKey(key) if err != nil { return 0, err } if client, ok := s.client.(s3HeadClient); ok { res, err := client.HeadObject(ctx, &s3.HeadObjectInput{Bucket: &s.bucket, Key: &full}) if err != nil { return 0, fmt.Errorf("storage: stat %q: %w", key, err) } if res.ContentLength == nil { return 0, fmt.Errorf("storage: stat %q: missing content length", key) } return *res.ContentLength, nil }
r, err := s.Get(ctx, key) if err != nil { return 0, err } defer r.Close() n, err := io.Copy(io.Discard, r) if err != nil { return 0, fmt.Errorf("storage: stat %q: %w", key, err) } return n, nil}
func (s *S3) PutIfAbsent(ctx context.Context, key string, r io.Reader) error { full, err := s.fullKey(key) if err != nil { return err } if _, err := s.client.PutObject(ctx, &s3.PutObjectInput{Bucket: &s.bucket, Key: &full, Body: r, IfNoneMatch: aws.String("*")}); err != nil { return fmt.Errorf("storage: put %q: %w", key, err) } return nil}
func (s *S3) Delete(ctx context.Context, key string) error { full, err := s.fullKey(key) if err != nil { return err } if _, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: &s.bucket, Key: &full, }); err != nil { return fmt.Errorf("storage: delete %q: %w", key, err) } return nil}