github.com/viant/toolbox@v0.34.5/storage/s3/service.go (about) 1 package s3 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/viant/toolbox/cred" 7 "io" 8 "net/url" 9 "strings" 10 11 "io/ioutil" 12 "time" 13 14 "github.com/aws/aws-sdk-go/aws" 15 "github.com/aws/aws-sdk-go/aws/credentials" 16 "github.com/aws/aws-sdk-go/aws/session" 17 "github.com/aws/aws-sdk-go/service/s3" 18 "github.com/aws/aws-sdk-go/service/s3/s3manager" 19 "github.com/viant/toolbox" 20 "github.com/viant/toolbox/storage" 21 "os" 22 ) 23 24 var defaultTime = time.Time{} 25 26 type service struct { 27 config *cred.Config 28 } 29 30 func listFolders(client *s3.S3, url *url.URL, result *[]storage.Object) error { 31 folderRequest := &s3.ListObjectsInput{ 32 Bucket: aws.String(url.Host), 33 Prefix: aws.String(url.Path[1:]), 34 Delimiter: aws.String("/"), 35 } 36 prefixes := make([]*s3.CommonPrefix, 0) 37 err := client.ListObjectsPages(folderRequest, 38 func(page *s3.ListObjectsOutput, lastPage bool) bool { 39 prefixes = append(prefixes, page.CommonPrefixes...) 40 return len(page.CommonPrefixes) > 0 41 }) 42 43 if err != nil { 44 if strings.Contains(err.Error(), "BucketRegionError") { 45 return nil 46 } 47 return err 48 } 49 for _, prefix := range prefixes { 50 pathURL := "s3://" + url.Host + "/" + *prefix.Prefix 51 var _, name = toolbox.URLSplit(pathURL) 52 var fileMode, _ = storage.NewFileMode("drw-rw-rw-") 53 var fileInfo = storage.NewFileInfo(name, 102, fileMode, defaultTime, fileMode.IsDir()) 54 var object = newStorageObject(pathURL, prefix, fileInfo) 55 *result = append(*result, object) 56 } 57 return nil 58 } 59 60 func listContent(client *s3.S3, parsedURL *url.URL, result *[]storage.Object) error { 61 var path = parsedURL.Path 62 63 folderRequest := &s3.ListObjectsInput{ 64 Bucket: aws.String(parsedURL.Host), 65 Delimiter: aws.String("/"), 66 } 67 if len(path) > 0 { 68 folderRequest.Prefix = aws.String(parsedURL.Path[1:]) 69 } 70 contents := make([]*s3.Object, 0) 71 err := client.ListObjectsPages(folderRequest, 72 func(page *s3.ListObjectsOutput, lastPage bool) bool { 73 contents = append(contents, page.Contents...) 74 return len(page.Contents) > 0 75 }) 76 77 if err != nil { 78 if strings.Contains(err.Error(), "BucketRegionError") { 79 return nil 80 } 81 return err 82 } 83 for _, content := range contents { 84 objectURL := "s3://" + parsedURL.Host + "/" + *content.Key 85 var _, name = toolbox.URLSplit(objectURL) 86 var fileMode, _ = storage.NewFileMode("-rw-rw-rw-") 87 var fileInfo = storage.NewFileInfo(name, *content.Size, fileMode, *content.LastModified, fileMode.IsDir()) 88 var object = newStorageObject(objectURL, content, fileInfo) 89 *result = append(*result, object) 90 } 91 return nil 92 } 93 94 func (s *service) getAwsConfig() (*aws.Config, error) { 95 if s.config.Secret == "" { 96 return aws.NewConfig().WithRegion(s.config.Region), nil 97 } 98 awsCredentials := credentials.NewStaticCredentials(s.config.Key, s.config.Secret, s.config.Token) 99 _, err := awsCredentials.Get() 100 if err != nil { 101 return nil, fmt.Errorf("bad credentials: %s", err) 102 } 103 return aws.NewConfig().WithRegion(s.config.Region).WithCredentials(awsCredentials), nil 104 } 105 106 func (s *service) List(URL string) ([]storage.Object, error) { 107 var result = make([]storage.Object, 0) 108 u, err := url.Parse(URL) 109 if err != nil { 110 return nil, fmt.Errorf("failed to parse : %v", err) 111 } 112 config, err := s.getAwsConfig() 113 if err != nil { 114 return nil, fmt.Errorf("failed to get aws config: %v", err) 115 } 116 client := s3.New(session.New(), config) 117 err = listFolders(client, u, &result) 118 if err == nil { 119 err = listContent(client, u, &result) 120 } 121 if err != nil { 122 return nil, fmt.Errorf("failed to get list content: %v", err) 123 } 124 return result, nil 125 } 126 127 func (s *service) Exists(URL string) (bool, error) { 128 objects, err := s.List(URL) 129 if err != nil { 130 return false, err 131 } 132 return len(objects) > 0, nil 133 } 134 135 func (s *service) StorageObject(URL string) (storage.Object, error) { 136 objects, err := s.List(URL) 137 if err != nil { 138 return nil, err 139 } 140 if len(objects) == 0 { 141 return nil, fmt.Errorf("Not found %v", URL) 142 } 143 return objects[0], nil 144 } 145 146 func (s *service) Download(object storage.Object) (io.ReadCloser, error) { 147 u, err := url.Parse(object.URL()) 148 if err != nil { 149 return nil, fmt.Errorf("failed to parse : %v", err) 150 } 151 config, err := s.getAwsConfig() 152 if err != nil { 153 return nil, fmt.Errorf("failed to get aws config: %v", err) 154 } 155 downloader := s3manager.NewDownloader(session.New(config)) 156 target := &s3.Object{} 157 _ = object.Unwrap(&target) 158 writer := toolbox.NewByteWriterAt() 159 _, err = downloader.Download(writer, 160 &s3.GetObjectInput{ 161 Bucket: aws.String(u.Host), 162 Key: aws.String(*target.Key), 163 }) 164 if err != nil { 165 return nil, fmt.Errorf("failed to download: %v", err) 166 } 167 return ioutil.NopCloser(bytes.NewReader(writer.Buffer)), nil 168 169 } 170 171 func (s *service) Upload(URL string, reader io.Reader) error { 172 return s.UploadWithMode(URL, storage.DefaultFileMode, reader) 173 } 174 175 func (s *service) UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error { 176 err := s.uploadContent(URL, reader) 177 if toolbox.IsNotFoundError(err) { 178 config, err := s.getAwsConfig() 179 if err != nil { 180 return err 181 } 182 if parserURL, err := url.Parse(URL); err == nil { 183 client := s3.New(session.New(config)) 184 if _, err := client.CreateBucket(&s3.CreateBucketInput{ 185 Bucket: &parserURL.Host, 186 }); err != nil { 187 return err 188 } 189 } 190 return s.uploadContent(URL, reader) 191 } 192 return err 193 } 194 195 func (s *service) uploadContent(URL string, reader io.Reader) error { 196 parsedURL, err := url.Parse(URL) 197 if err != nil { 198 return err 199 } 200 config, err := s.getAwsConfig() 201 if err != nil { 202 return err 203 } 204 uploader := s3manager.NewUploader(session.New(config)) 205 _, err = uploader.Upload(&s3manager.UploadInput{ 206 Body: reader, 207 Bucket: aws.String(parsedURL.Host), 208 Key: aws.String(parsedURL.Path), 209 }) 210 if err != nil { 211 return toolbox.ReclassifyNotFoundIfMatched(err, URL) 212 } 213 return nil 214 } 215 216 func (s *service) Delete(object storage.Object) error { 217 parsedURL, err := url.Parse(object.URL()) 218 if err != nil { 219 return err 220 } 221 222 if object.IsFolder() { 223 var objects = []storage.Object{} 224 objects, err = s.List(object.URL()) 225 if err != nil { 226 return err 227 } 228 for _, object := range objects { 229 if err := s.Delete(object); err != nil { 230 return err 231 } 232 } 233 return nil 234 } 235 236 target := &s3.Object{} 237 object.Unwrap(&target) 238 request := &s3.DeleteObjectInput{ 239 Bucket: aws.String(parsedURL.Host), 240 Key: target.Key, 241 } 242 config, err := s.getAwsConfig() 243 if err != nil { 244 return err 245 } 246 client := s3.New(session.New(), config) 247 client.DeleteObject(request) 248 return nil 249 } 250 251 func (s *service) Register(schema string, service storage.Service) error { 252 return fmt.Errorf("Unsupported") 253 } 254 255 //DownloadWithURL downloads content for passed in object URL 256 func (s *service) DownloadWithURL(URL string) (io.ReadCloser, error) { 257 object, err := s.StorageObject(URL) 258 if err != nil { 259 return nil, err 260 } 261 return s.Download(object) 262 } 263 264 func (s *service) Close() error { 265 return nil 266 } 267 268 //NewService creates a new aws storage service 269 func NewService(config *cred.Config) storage.Service { 270 return &service{config: config} 271 }