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  }