github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/blobstore/oss.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package blobstore 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "path" 23 "strconv" 24 25 "github.com/aliyun/aliyun-oss-go-sdk/oss" 26 ) 27 28 const ( 29 enabled = "Enabled" 30 ) 31 32 // OSSBlobstore provides an Aliyun OSS implementation of the Blobstore interface 33 type OSSBlobstore struct { 34 bucket *oss.Bucket 35 bucketName string 36 enableVersion bool 37 prefix string 38 } 39 40 var _ Blobstore = &OSSBlobstore{} 41 42 // NewOSSBlobstore creates a new instance of a OSSBlobstore 43 func NewOSSBlobstore(ossClient *oss.Client, bucketName, prefix string) (*OSSBlobstore, error) { 44 prefix = normalizePrefix(prefix) 45 bucket, err := ossClient.Bucket(bucketName) 46 if err != nil { 47 return nil, err 48 } 49 // check if bucket enable versioning 50 versionStatus, err := ossClient.GetBucketVersioning(bucketName) 51 if err != nil { 52 return nil, err 53 } 54 return &OSSBlobstore{ 55 bucket: bucket, 56 bucketName: bucketName, 57 prefix: prefix, 58 enableVersion: versionStatus.Status == enabled, 59 }, nil 60 } 61 62 func (ob *OSSBlobstore) Path() string { 63 return path.Join(ob.bucketName, ob.prefix) 64 } 65 66 func (ob *OSSBlobstore) Exists(_ context.Context, key string) (bool, error) { 67 return ob.bucket.IsObjectExist(ob.absKey(key)) 68 } 69 70 func (ob *OSSBlobstore) Get(ctx context.Context, key string, br BlobRange) (io.ReadCloser, string, error) { 71 absKey := ob.absKey(key) 72 meta, err := ob.bucket.GetObjectMeta(absKey) 73 74 if isNotFoundErr(err) { 75 return nil, "", NotFound{"oss://" + path.Join(ob.bucketName, absKey)} 76 } 77 78 if br.isAllRange() { 79 reader, err := ob.bucket.GetObject(absKey) 80 if err != nil { 81 return nil, "", err 82 } 83 return reader, ob.getVersion(meta), nil 84 } 85 size, err := strconv.ParseInt(meta.Get(oss.HTTPHeaderContentLength), 10, 64) 86 if err != nil { 87 return nil, "", err 88 } 89 posBr := br.positiveRange(size) 90 reader, err := ob.bucket.GetObject(absKey, oss.Range(posBr.offset, posBr.offset+posBr.length-1)) 91 if err != nil { 92 return nil, "", err 93 } 94 return reader, ob.getVersion(meta), nil 95 } 96 97 func (ob *OSSBlobstore) Put(ctx context.Context, key string, totalSize int64, reader io.Reader) (string, error) { 98 var meta http.Header 99 if err := ob.bucket.PutObject(ob.absKey(key), reader, oss.GetResponseHeader(&meta)); err != nil { 100 return "", err 101 } 102 return ob.getVersion(meta), nil 103 } 104 105 func (ob *OSSBlobstore) CheckAndPut(ctx context.Context, expectedVersion, key string, totalSize int64, reader io.Reader) (string, error) { 106 var options []oss.Option 107 if expectedVersion != "" { 108 options = append(options, oss.VersionId(expectedVersion)) 109 } 110 var meta http.Header 111 options = append(options, oss.GetResponseHeader(&meta)) 112 if err := ob.bucket.PutObject(ob.absKey(key), reader, options...); err != nil { 113 ossErr, ok := err.(oss.ServiceError) 114 if ok { 115 return "", CheckAndPutError{ 116 Key: key, 117 ExpectedVersion: expectedVersion, 118 ActualVersion: fmt.Sprintf("unknown (OSS error code %d)", ossErr.StatusCode)} 119 } 120 return "", err 121 } 122 return ob.getVersion(meta), nil 123 } 124 125 func (ob *OSSBlobstore) Concatenate(ctx context.Context, key string, sources []string) (string, error) { 126 return "", fmt.Errorf("Conjoin is not implemented for OSSBlobstore") 127 } 128 129 func (ob *OSSBlobstore) absKey(key string) string { 130 return path.Join(ob.prefix, key) 131 } 132 133 func (ob *OSSBlobstore) getVersion(meta http.Header) string { 134 if ob.enableVersion { 135 return oss.GetVersionId(meta) 136 } 137 return "" 138 } 139 140 func normalizePrefix(prefix string) string { 141 for len(prefix) > 0 && prefix[0] == '/' { 142 prefix = prefix[1:] 143 } 144 return prefix 145 } 146 147 func isNotFoundErr(err error) bool { 148 switch err.(type) { 149 case oss.ServiceError: 150 if err.(oss.ServiceError).StatusCode == 404 { 151 return true 152 } 153 } 154 return false 155 }