This repository has no description
1package storage
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "strings"
9
10 awsconfig "github.com/aws/aws-sdk-go-v2/config"
11 "github.com/aws/aws-sdk-go-v2/service/s3"
12 "github.com/aws/aws-sdk-go-v2/service/s3/types"
13)
14
15type S3 struct {
16 bucket string
17 prefix string
18 client *s3.Client
19}
20
21func NewS3(ctx context.Context, bucket, prefix string) (*S3, error) {
22 if bucket == "" {
23 return nil, fmt.Errorf("storage: s3 backend requires a bucket")
24 }
25 sdkConfig, err := awsconfig.LoadDefaultConfig(ctx)
26 if err != nil {
27 return nil, fmt.Errorf("storage: load s3 config: %w", err)
28 }
29 client := s3.NewFromConfig(sdkConfig)
30 versioning, err := client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
31 Bucket: &bucket,
32 })
33 if err != nil {
34 return nil, fmt.Errorf("storage: check s3 bucket versioning: %w", err)
35 }
36 if versioning.Status != "" {
37 return nil, fmt.Errorf("storage: s3 cache bucket must not use versioning")
38 }
39 return &S3{
40 bucket: bucket,
41 prefix: strings.Trim(prefix, "/"),
42 client: client,
43 }, nil
44}
45
46func (s *S3) fullKey(key string) (string, error) {
47 if err := ValidateKey(key); err != nil {
48 return "", err
49 }
50 if s.prefix == "" {
51 return key, nil
52 }
53 return s.prefix + "/" + key, nil
54}
55
56func (s *S3) Get(ctx context.Context, key string) (io.ReadCloser, error) {
57 full, err := s.fullKey(key)
58 if err != nil {
59 return nil, err
60 }
61 res, err := s.client.GetObject(ctx, &s3.GetObjectInput{
62 Bucket: &s.bucket,
63 Key: &full,
64 })
65 if err != nil {
66 var nsk *types.NoSuchKey
67 if errors.As(err, &nsk) {
68 return nil, ErrNotExist
69 }
70 return nil, fmt.Errorf("storage: get %q: %w", key, err)
71 }
72 return res.Body, nil
73}
74
75func (s *S3) Put(ctx context.Context, key string, r io.Reader) error {
76 full, err := s.fullKey(key)
77 if err != nil {
78 return err
79 }
80 if _, err := s.client.PutObject(ctx, &s3.PutObjectInput{
81 Bucket: &s.bucket,
82 Key: &full,
83 Body: r,
84 }); err != nil {
85 return fmt.Errorf("storage: put %q: %w", key, err)
86 }
87 return nil
88}
89
90func (s *S3) Delete(ctx context.Context, key string) error {
91 full, err := s.fullKey(key)
92 if err != nil {
93 return err
94 }
95 if _, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{
96 Bucket: &s.bucket,
97 Key: &full,
98 }); err != nil {
99 return fmt.Errorf("storage: delete %q: %w", key, err)
100 }
101 return nil
102}