github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/storage/s3.go (about)

     1  // Copyright (c) 2016 Readium Foundation
     2  //
     3  // Redistribution and use in source and binary forms, with or without modification,
     4  // are permitted provided that the following conditions are met:
     5  //
     6  // 1. Redistributions of source code must retain the above copyright notice, this
     7  //    list of conditions and the following disclaimer.
     8  // 2. Redistributions in binary form must reproduce the above copyright notice,
     9  //    this list of conditions and the following disclaimer in the documentation and/or
    10  //    other materials provided with the distribution.
    11  // 3. Neither the name of the organization nor the names of its contributors may be
    12  //    used to endorse or promote products derived from this software without specific
    13  //    prior written permission
    14  //
    15  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    16  // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    17  // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    18  // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
    19  // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    20  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    21  // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    22  // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    23  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    24  // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    25  
    26  package storage
    27  
    28  import (
    29  	"fmt"
    30  	"io"
    31  
    32  	"github.com/aws/aws-sdk-go/aws"
    33  	"github.com/aws/aws-sdk-go/aws/credentials"
    34  	"github.com/aws/aws-sdk-go/aws/session"
    35  	"github.com/aws/aws-sdk-go/service/s3"
    36  )
    37  
    38  type s3store struct {
    39  	bucket string
    40  	client *s3.S3
    41  }
    42  
    43  type s3item struct {
    44  	bucket string
    45  	key    string
    46  	store  *s3store
    47  }
    48  
    49  func (i s3item) Key() string {
    50  	return i.key
    51  }
    52  
    53  func (i s3item) PublicURL() string {
    54  	return fmt.Sprintf("http://%s/%s/%s", i.store.client.Endpoint, i.bucket, i.key)
    55  }
    56  
    57  func (i s3item) Contents() (io.ReadCloser, error) {
    58  	resp, err := i.store.client.GetObject(&s3.GetObjectInput{
    59  		Bucket: aws.String(i.store.bucket),
    60  		Key:    aws.String(i.key),
    61  	})
    62  
    63  	return resp.Body, err
    64  }
    65  
    66  func (s *s3store) Add(key string, r io.ReadSeeker) (Item, error) {
    67  	_, err := s.client.PutObject(&s3.PutObjectInput{
    68  		Bucket: aws.String(s.bucket),
    69  		Key:    aws.String(key),
    70  		Body:   r,
    71  	})
    72  
    73  	item := s3item{bucket: s.bucket, key: key, store: s}
    74  
    75  	return item, err
    76  }
    77  
    78  func (s *s3store) Get(key string) (Item, error) {
    79  	_, err := s.client.HeadObject(&s3.HeadObjectInput{
    80  		Bucket: aws.String(s.bucket),
    81  		Key:    aws.String(key),
    82  	})
    83  	return s3item{bucket: s.bucket, key: key, store: s}, err
    84  }
    85  
    86  func (s *s3store) Remove(key string) error {
    87  	_, err := s.client.DeleteObject(&s3.DeleteObjectInput{
    88  		Bucket: aws.String(s.bucket),
    89  		Key:    aws.String(key),
    90  	})
    91  
    92  	return err
    93  }
    94  
    95  func (s *s3store) List() ([]Item, error) {
    96  	objects, err := s.client.ListObjects(&s3.ListObjectsInput{
    97  		Bucket: aws.String(s.bucket),
    98  	})
    99  
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	var items []Item
   105  
   106  	for _, o := range objects.Contents {
   107  		items = append(items, s3item{bucket: s.bucket, key: *o.Key, store: s})
   108  	}
   109  
   110  	return items, nil
   111  }
   112  
   113  // S3Config structure
   114  type S3Config struct {
   115  	Bucket   string
   116  	Endpoint string
   117  	Region   string
   118  
   119  	ID     string
   120  	Secret string
   121  	Token  string
   122  
   123  	DisableSSL     bool
   124  	ForcePathStyle bool
   125  }
   126  
   127  // S3 inits and S3 storage
   128  func S3(config S3Config) (Store, error) {
   129  	awsConfig := &aws.Config{
   130  		CredentialsChainVerboseErrors: aws.Bool(true),
   131  		DisableSSL:                    aws.Bool(config.DisableSSL),
   132  		S3ForcePathStyle:              aws.Bool(config.ForcePathStyle),
   133  		Region:                        aws.String(config.Region),
   134  		Endpoint:                      aws.String(config.Endpoint)}
   135  
   136  	// Credentials defaults to a chain of credential providers to search for credentials in environment
   137  	// variables, shared credential file, and EC2 Instance Roles.
   138  	// Therefore, we only explicitly define static credentials if these are present in config
   139  	if config.ID != "" && config.Secret != "" {
   140  		awsConfig.Credentials = credentials.NewStaticCredentials(config.ID, config.Secret, config.Token)
   141  	}
   142  
   143  	s3session, err := session.NewSession(awsConfig)
   144  	return &s3store{client: s3.New(s3session), bucket: config.Bucket}, err
   145  }