yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/objectstore/buckets.go (about) 1 // Copyright 2019 Yunion 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 objectstore 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "strings" 23 "time" 24 25 "yunion.io/x/pkg/errors" 26 "yunion.io/x/pkg/utils" 27 "yunion.io/x/s3cli" 28 29 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/cloudmux/pkg/multicloud" 32 ) 33 34 type SBucket struct { 35 multicloud.SBaseBucket 36 multicloud.STagBase 37 38 client IBucketProvider 39 40 Name string 41 Location string 42 CreatedAt time.Time 43 StorageClass string 44 } 45 46 func (bucket *SBucket) GetIBucketProvider() IBucketProvider { 47 return bucket.client 48 } 49 50 func (bucket *SBucket) GetId() string { 51 return "" 52 } 53 54 func (bucket *SBucket) GetStatus() string { 55 return api.BUCKET_STATUS_READY 56 } 57 58 func (bucket *SBucket) GetProjectId() string { 59 return "" 60 } 61 62 func (bucket *SBucket) IsEmulated() bool { 63 return false 64 } 65 66 func (bucket *SBucket) Refresh() error { 67 return nil 68 } 69 70 func (bucket *SBucket) GetGlobalId() string { 71 return bucket.Name 72 } 73 74 func (bucket *SBucket) GetName() string { 75 return bucket.Name 76 } 77 78 func (bucket *SBucket) GetAcl() cloudprovider.TBucketACLType { 79 acl, _ := bucket.client.GetIBucketAcl(bucket.Name) 80 return acl 81 } 82 83 func (bucket *SBucket) SetAcl(aclStr cloudprovider.TBucketACLType) error { 84 return bucket.client.SetIBucketAcl(bucket.Name, aclStr) 85 } 86 87 func (bucket *SBucket) GetLocation() string { 88 return bucket.Location 89 } 90 91 func (bucket *SBucket) GetIRegion() cloudprovider.ICloudRegion { 92 return bucket.client 93 } 94 95 func (bucket *SBucket) GetCreatedAt() time.Time { 96 return bucket.CreatedAt 97 } 98 99 func (bucket *SBucket) GetStorageClass() string { 100 return bucket.StorageClass 101 } 102 103 func (bucket *SBucket) GetStats() cloudprovider.SBucketStats { 104 stats, _ := cloudprovider.GetIBucketStats(bucket) 105 return stats 106 } 107 108 func joinPath(ep, path string) string { 109 return strings.TrimRight(ep, "/") + "/" + strings.TrimLeft(path, "/") 110 } 111 112 func (bucket *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl { 113 return []cloudprovider.SBucketAccessUrl{ 114 { 115 Url: joinPath(bucket.client.GetEndpoint(), bucket.Name), 116 Description: fmt.Sprintf("%s", bucket.Location), 117 Primary: true, 118 }, 119 } 120 } 121 122 func (bucket *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) { 123 ret := cloudprovider.SListObjectResult{} 124 result, err := bucket.client.S3Client().ListObjectsQuery(bucket.Name, prefix, marker, delimiter, maxCount) 125 if err != nil { 126 return ret, errors.Wrap(err, "ListObjectsQuery") 127 } 128 ret.NextMarker = result.NextMarker 129 ret.IsTruncated = result.IsTruncated 130 ret.CommonPrefixes = make([]cloudprovider.ICloudObject, len(result.CommonPrefixes)) 131 for i := range result.CommonPrefixes { 132 ret.CommonPrefixes[i] = &SObject{ 133 bucket: bucket, 134 SBaseCloudObject: cloudprovider.SBaseCloudObject{ 135 Key: result.CommonPrefixes[i].Prefix, 136 }, 137 } 138 } 139 ret.Objects = make([]cloudprovider.ICloudObject, len(result.Contents)) 140 for i := range result.Contents { 141 object := result.Contents[i] 142 ret.Objects[i] = &SObject{ 143 bucket: bucket, 144 SBaseCloudObject: cloudprovider.SBaseCloudObject{ 145 StorageClass: object.StorageClass, 146 Key: object.Key, 147 SizeBytes: object.Size, 148 ETag: object.ETag, 149 LastModified: object.LastModified, 150 }, 151 } 152 } 153 return ret, nil 154 } 155 156 func (bucket *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) { 157 doneCh := make(chan struct{}) 158 defer close(doneCh) 159 160 ret := make([]cloudprovider.ICloudObject, 0) 161 objectCh := bucket.client.S3Client().ListObjects(bucket.Name, prefix, isRecursive, doneCh) 162 for object := range objectCh { 163 if object.Err != nil { 164 return nil, errors.Wrap(object.Err, "ListObjects") 165 } 166 if !isRecursive && prefix == object.Key { 167 continue 168 } 169 obj := &SObject{ 170 bucket: bucket, 171 SBaseCloudObject: cloudprovider.SBaseCloudObject{ 172 StorageClass: object.StorageClass, 173 Key: object.Key, 174 SizeBytes: object.Size, 175 ETag: object.ETag, 176 LastModified: object.LastModified, 177 }, 178 } 179 ret = append(ret, obj) 180 } 181 return ret, nil 182 } 183 184 func metaInitOptions(meta http.Header) s3cli.PutObjectOptions { 185 opts := s3cli.PutObjectOptions{} 186 if meta != nil { 187 val := meta.Get(cloudprovider.META_HEADER_CONTENT_TYPE) 188 if len(val) > 0 { 189 opts.ContentType = val 190 } 191 val = meta.Get(cloudprovider.META_HEADER_CACHE_CONTROL) 192 if len(val) > 0 { 193 opts.CacheControl = val 194 } 195 val = meta.Get(cloudprovider.META_HEADER_CONTENT_DISPOSITION) 196 if len(val) > 0 { 197 opts.ContentDisposition = val 198 } 199 val = meta.Get(cloudprovider.META_HEADER_CONTENT_ENCODING) 200 if len(val) > 0 { 201 opts.ContentEncoding = val 202 } 203 val = meta.Get(cloudprovider.META_HEADER_CONTENT_LANGUAGE) 204 if len(val) > 0 { 205 opts.ContentLanguage = val 206 } 207 userMeta := make(map[string]string) 208 for k, v := range meta { 209 if utils.IsInStringArray(k, []string{ 210 cloudprovider.META_HEADER_CONTENT_TYPE, 211 cloudprovider.META_HEADER_CACHE_CONTROL, 212 cloudprovider.META_HEADER_CONTENT_DISPOSITION, 213 cloudprovider.META_HEADER_CONTENT_ENCODING, 214 cloudprovider.META_HEADER_CONTENT_LANGUAGE, 215 }) { 216 continue 217 } 218 if len(v) > 0 { 219 userMeta[http.CanonicalHeaderKey(k)] = v[0] 220 } 221 } 222 opts.UserMetadata = userMeta 223 } 224 return opts 225 } 226 227 func (bucket *SBucket) PutObject(ctx context.Context, key string, input io.Reader, sizeBytes int64, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { 228 opts := metaInitOptions(meta) 229 if len(storageClassStr) > 0 { 230 opts.StorageClass = storageClassStr 231 } 232 opts.PartSize = uint64(cloudprovider.MAX_PUT_OBJECT_SIZEBYTES) 233 _, err := bucket.client.S3Client().PutObjectDo(ctx, bucket.Name, key, input, "", "", sizeBytes, opts) 234 if err != nil { 235 return errors.Wrap(err, "PutObjectWithContext") 236 } 237 obj, err := cloudprovider.GetIObject(bucket, key) 238 if err != nil { 239 return errors.Wrap(err, "cloudprovider.GetIObject") 240 } 241 if len(cannedAcl) == 0 { 242 cannedAcl = bucket.GetAcl() 243 } 244 err = obj.SetAcl(cannedAcl) 245 if err != nil && errors.Cause(err) != cloudprovider.ErrNotImplemented { 246 return errors.Wrap(err, "obj.SetAcl") 247 } 248 return nil 249 } 250 251 func (bucket *SBucket) NewMultipartUpload(ctx context.Context, key string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) (string, error) { 252 opts := metaInitOptions(meta) 253 if len(storageClassStr) > 0 { 254 opts.StorageClass = storageClassStr 255 } 256 result, err := bucket.client.S3Client().InitiateMultipartUpload(ctx, bucket.Name, key, opts) 257 if err != nil { 258 return "", errors.Wrap(err, "InitiateMultipartUpload") 259 } 260 return result.UploadID, nil 261 } 262 263 func (bucket *SBucket) UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64, offset, totalSize int64) (string, error) { 264 part, err := bucket.client.S3Client().UploadPart(ctx, bucket.Name, key, uploadId, input, partIndex, "", "", partSize, nil) 265 if err != nil { 266 return "", errors.Wrap(err, "UploadPart") 267 } 268 return part.ETag, nil 269 } 270 271 func (bucket *SBucket) CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error { 272 complete := s3cli.CompleteMultipartUpload{} 273 complete.Parts = make([]s3cli.CompletePart, len(partEtags)) 274 for i := 0; i < len(partEtags); i += 1 { 275 complete.Parts[i] = s3cli.CompletePart{ 276 PartNumber: i + 1, 277 ETag: partEtags[i], 278 } 279 } 280 _, err := bucket.client.S3Client().CompleteMultipartUpload(ctx, bucket.Name, key, uploadId, complete) 281 if err != nil { 282 return errors.Wrap(err, "CompleteMultipartUpload") 283 } 284 return nil 285 } 286 287 func (bucket *SBucket) AbortMultipartUpload(ctx context.Context, key string, uploadId string) error { 288 err := bucket.client.S3Client().AbortMultipartUpload(ctx, bucket.Name, key, uploadId) 289 if err != nil { 290 return errors.Wrap(err, "AbortMultipartUpload") 291 } 292 return nil 293 } 294 295 func (bucket *SBucket) DeleteObject(ctx context.Context, key string) error { 296 err := bucket.client.S3Client().RemoveObject(bucket.Name, key) 297 if err != nil { 298 return errors.Wrap(err, "RemoveObject") 299 } 300 return nil 301 } 302 303 func (bucket *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) { 304 if method != "GET" && method != "PUT" && method != "DELETE" { 305 return "", errors.Error("unsupported method") 306 } 307 url, err := bucket.client.S3Client().Presign(method, bucket.Name, key, expire, nil) 308 if err != nil { 309 return "", errors.Wrap(err, "Presign") 310 } 311 return url.String(), nil 312 } 313 314 func (bucket *SBucket) CopyObject(ctx context.Context, destKey string, srcBucket, srcKey string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, dstMeta http.Header) error { 315 meta := make(map[string]string) 316 if dstMeta != nil { 317 for k, v := range dstMeta { 318 meta[http.CanonicalHeaderKey(k)] = v[0] 319 } 320 } 321 if len(storageClassStr) > 0 { 322 meta[http.CanonicalHeaderKey("x-amz-storage-class")] = storageClassStr 323 } 324 dest, err := s3cli.NewDestinationInfo(bucket.Name, destKey, nil, meta) 325 if err != nil { 326 return errors.Wrap(err, "NewDestinationInfo") 327 } 328 src := s3cli.NewSourceInfo(srcBucket, srcKey, nil) 329 err = bucket.client.S3Client().CopyObject(dest, src) 330 if err != nil { 331 return errors.Wrap(err, "CopyObject") 332 } 333 obj, err := cloudprovider.GetIObject(bucket, destKey) 334 if err != nil { 335 return errors.Wrap(err, "GetIObject") 336 } 337 if len(cannedAcl) == 0 { 338 cannedAcl = bucket.GetAcl() 339 } 340 err = obj.SetAcl(cannedAcl) 341 if err != nil && errors.Cause(err) != cloudprovider.ErrNotImplemented { 342 return errors.Wrap(err, "obj.SetAcl") 343 } 344 return nil 345 } 346 347 func (bucket *SBucket) GetObject(ctx context.Context, key string, rangeOpt *cloudprovider.SGetObjectRange) (io.ReadCloser, error) { 348 opts := s3cli.GetObjectOptions{} 349 if rangeOpt != nil { 350 opts.SetRange(rangeOpt.Start, rangeOpt.End) 351 } 352 output, err := bucket.client.S3Client().GetObject(bucket.Name, key, opts) 353 if err != nil { 354 return nil, errors.Wrap(err, "GetObject") 355 } 356 return output, nil 357 } 358 359 func (bucket *SBucket) CopyPart(ctx context.Context, key string, uploadId string, partNumber int, srcBucket string, srcKey string, srcOffset int64, srcLength int64) (string, error) { 360 result, err := bucket.client.S3Client().CopyObjectPartDo(ctx, srcBucket, srcKey, bucket.Name, key, uploadId, partNumber, srcOffset, srcLength, nil) 361 if err != nil { 362 return "", errors.Wrap(err, "CopyObjectPartDo") 363 } 364 return result.ETag, nil 365 }