github.com/klaytn/klaytn@v1.12.1/storage/database/s3filedb.go (about) 1 // Copyright 2020 The klaytn Authors 2 // This file is part of the klaytn library. 3 // 4 // The klaytn library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The klaytn library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>. 16 // 17 // FileDB implementation of AWS S3. 18 // 19 // [WARN] Using this DB may cause pricing in your AWS account. 20 // 21 // You need to set AWS credentials to access to S3. 22 // $ export AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY 23 // $ export AWS_SECRET_ACCESS_KEY=YOUR_SECRET 24 25 package database 26 27 import ( 28 "bytes" 29 "fmt" 30 "io" 31 "time" 32 33 "github.com/klaytn/klaytn/common/hexutil" 34 35 "github.com/aws/aws-sdk-go/aws" 36 "github.com/aws/aws-sdk-go/aws/client" 37 "github.com/aws/aws-sdk-go/aws/session" 38 "github.com/aws/aws-sdk-go/service/s3" 39 "github.com/klaytn/klaytn/log" 40 ) 41 42 // s3FileDB is an implementation of fileDB based on AWS S3. 43 // It stores the data to the designated AWS S3 bucket. 44 type s3FileDB struct { 45 region string 46 endpoint string 47 bucket string 48 s3 *s3.S3 49 logger log.Logger 50 } 51 52 // newS3FileDB returns a new s3FileDB with the given region, endpoint and bucketName. 53 // If the given bucket does not exist, it creates one. 54 func newS3FileDB(region, endpoint, bucketName string) (*s3FileDB, error) { 55 localLogger := logger.NewWith("endpoint", endpoint, "bucketName", bucketName) 56 sessionConf, err := session.NewSession(&aws.Config{ 57 Retryer: CustomRetryer{ 58 DefaultRetryer: client.DefaultRetryer{ 59 NumMaxRetries: dynamoMaxRetry, 60 MaxRetryDelay: time.Second, 61 MaxThrottleDelay: time.Second, 62 }, 63 }, 64 Region: aws.String(region), 65 Endpoint: aws.String(endpoint), 66 S3ForcePathStyle: aws.Bool(true), 67 }) 68 if err != nil { 69 localLogger.Error("failed to create session", "region", region) 70 return nil, err 71 } 72 73 s3DB := &s3FileDB{ 74 region: region, 75 endpoint: endpoint, 76 bucket: bucketName, 77 s3: s3.New(sessionConf), 78 logger: localLogger, 79 } 80 81 exist, err := s3DB.hasBucket(bucketName) 82 if err != nil { 83 localLogger.Error("failed to retrieve a bucket list", "err", err) 84 return nil, err 85 } 86 87 if !exist { 88 localLogger.Warn("creating a S3 bucket. You will be CHARGED until the bucket is deleted") 89 _, err = s3DB.s3.CreateBucket(&s3.CreateBucketInput{ 90 Bucket: aws.String(bucketName), 91 }) 92 if err != nil { 93 localLogger.Error("failed to create a bucket", "err", err) 94 return nil, err 95 } 96 } 97 localLogger.Info("successfully created S3 session") 98 return s3DB, nil 99 } 100 101 // hasBucket returns if the bucket exists in the endpoint of s3FileDB. 102 func (s3DB *s3FileDB) hasBucket(bucketName string) (bool, error) { 103 output, err := s3DB.s3.ListBuckets(&s3.ListBucketsInput{}) 104 if err != nil { 105 return false, err 106 } 107 108 bucketExist := false 109 for _, bucket := range output.Buckets { 110 if bucketName == *bucket.Name { 111 bucketExist = true 112 break 113 } 114 } 115 return bucketExist, nil 116 } 117 118 // write puts list of items to its bucket and returns the list of URIs. 119 func (s3DB *s3FileDB) write(item item) (string, error) { 120 o := &s3.PutObjectInput{ 121 Bucket: aws.String(s3DB.bucket), 122 Key: aws.String(hexutil.Encode(item.key)), 123 Body: bytes.NewReader(item.val), 124 ContentType: aws.String("application/octet-stream"), 125 } 126 127 if _, err := s3DB.s3.PutObject(o); err != nil { 128 return "", fmt.Errorf("failed to write item to S3. key: %v, err: %w", string(item.key), err) 129 } 130 131 return hexutil.Encode(item.key), nil 132 } 133 134 // read gets the data from the bucket with the given key. 135 func (s3DB *s3FileDB) read(key []byte) ([]byte, error) { 136 output, err := s3DB.s3.GetObject(&s3.GetObjectInput{ 137 Bucket: aws.String(s3DB.bucket), 138 Key: aws.String(hexutil.Encode(key)), 139 ResponseContentType: aws.String("application/octet-stream"), 140 }) 141 if err != nil { 142 return nil, err 143 } 144 145 returnVal, err := io.ReadAll(output.Body) 146 if err != nil { 147 return nil, err 148 } 149 150 return returnVal, nil 151 } 152 153 // delete removes the data with the given key from the bucket. 154 // No error is returned if the data with the given key does not exist. 155 func (s3DB *s3FileDB) delete(key []byte) error { 156 _, err := s3DB.s3.DeleteObject(&s3.DeleteObjectInput{ 157 Bucket: aws.String(s3DB.bucket), 158 Key: aws.String(hexutil.Encode(key)), 159 }) 160 return err 161 } 162 163 // deleteBucket removes the bucket 164 func (s3DB *s3FileDB) deleteBucket() { 165 if _, err := s3DB.s3.DeleteBucket(&s3.DeleteBucketInput{Bucket: aws.String(s3DB.bucket)}); err != nil { 166 s3DB.logger.Error("failed to delete the test bucket", "err", err, "bucketName", s3DB.bucket) 167 } 168 }