/
s3.go
160 lines (131 loc) · 4.04 KB
/
s3.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package storage
import (
"context"
"io"
"path"
"time"
"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"
"github.com/pkg/errors"
"github.com/spf13/afero"
"github.com/transcom/mymove/pkg/models"
)
// S3 implements the file storage API using S3.
type S3 struct {
bucket string
keyNamespace string
client *s3.Client
fs *afero.Afero
tempFs *afero.Afero
}
// NewS3 creates a new S3 using the provided AWS session.
func NewS3(bucket, keyNamespace string, cfg aws.Config) *S3 {
var fs = afero.NewMemMapFs()
var tempFs = afero.NewMemMapFs()
client := s3.NewFromConfig(cfg)
return &S3{
bucket: bucket,
keyNamespace: keyNamespace,
client: client,
fs: &afero.Afero{Fs: fs},
tempFs: &afero.Afero{Fs: tempFs},
}
}
// Store stores the content from an io.ReadSeeker at the specified key.
func (s *S3) Store(key string, data io.ReadSeeker, checksum string, tags *string) (*StoreResult, error) {
if key == "" {
return nil, errors.New("A valid StorageKey must be set before data can be uploaded")
}
namespacedKey := path.Join(s.keyNamespace, key)
input := &s3.PutObjectInput{
Bucket: &s.bucket,
Key: &namespacedKey,
Body: data,
ContentMD5: &checksum,
ServerSideEncryption: types.ServerSideEncryptionAes256,
}
if tags != nil {
input.Tagging = tags
}
if _, err := s.client.PutObject(context.Background(),
input); err != nil {
return nil, errors.Wrap(err, "put on S3 failed")
}
return &StoreResult{}, nil
}
// Delete deletes an object at a specified key
// Use with caution, deletions are disabled on our S3 buckets as per our ATO.
func (s *S3) Delete(key string) error {
namespacedKey := path.Join(s.keyNamespace, key)
input := &s3.DeleteObjectInput{
Bucket: &s.bucket,
Key: &namespacedKey,
}
_, err := s.client.DeleteObject(context.Background(), input)
if err != nil {
return errors.Wrap(err, "delete on S3 failed")
}
return nil
}
// Fetch retrieves an object at a specified key and stores it in a tempfile. The
// path to this file is returned.
//
// It is the caller's responsibility to cleanup this file.
func (s *S3) Fetch(key string) (io.ReadCloser, error) {
namespacedKey := path.Join(s.keyNamespace, key)
input := &s3.GetObjectInput{
Bucket: &s.bucket,
Key: &namespacedKey,
}
getObjectOutput, err := s.client.GetObject(context.Background(), input)
if err != nil {
return nil, errors.Wrap(err, "get object on S3 failed")
}
return getObjectOutput.Body, nil
}
// FileSystem returns the underlying afero filesystem
func (s *S3) FileSystem() *afero.Afero {
return s.fs
}
// TempFileSystem returns the temporary afero filesystem
func (s *S3) TempFileSystem() *afero.Afero {
return s.tempFs
}
// PresignedURL returns a URL that provides access to a file for 15 minutes.
func (s *S3) PresignedURL(key string, contentType string) (string, error) {
namespacedKey := path.Join(s.keyNamespace, key)
presignClient := s3.NewPresignClient(s.client)
req, err := presignClient.PresignGetObject(context.Background(),
&s3.GetObjectInput{
Bucket: &s.bucket,
Key: &namespacedKey,
ResponseContentType: &contentType,
ResponseContentDisposition: models.StringPointer("attachment"),
},
func(opts *s3.PresignOptions) {
opts.Expires = 15 * time.Minute
},
)
if err != nil {
return "", errors.Wrap(err, "could not generate presigned URL")
}
return req.URL, nil
}
// Tags returns the tags for a specified key
func (s *S3) Tags(key string) (map[string]string, error) {
tags := make(map[string]string)
namespacedKey := path.Join(s.keyNamespace, key)
input := &s3.GetObjectTaggingInput{
Bucket: &s.bucket,
Key: &namespacedKey,
}
result, err := s.client.GetObjectTagging(context.Background(), input)
if err != nil {
return tags, errors.Wrap(err, "get object tagging on s3 failed")
}
for _, tag := range result.TagSet {
tags[*tag.Key] = *tag.Value
}
return tags, nil
}