yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/cloudprovider/objectstore.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 cloudprovider 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "regexp" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 28 "yunion.io/x/jsonutils" 29 "yunion.io/x/log" 30 "yunion.io/x/pkg/errors" 31 "yunion.io/x/s3cli" 32 33 "yunion.io/x/onecloud/pkg/httperrors" 34 ) 35 36 type TBucketACLType string 37 38 const ( 39 // 50 MB 40 MAX_PUT_OBJECT_SIZEBYTES = int64(1024 * 1024 * 50) 41 42 // ACLDefault = TBucketACLType("default") 43 44 ACLPrivate = TBucketACLType(s3cli.CANNED_ACL_PRIVATE) 45 ACLAuthRead = TBucketACLType(s3cli.CANNED_ACL_AUTH_READ) 46 ACLPublicRead = TBucketACLType(s3cli.CANNED_ACL_PUBLIC_READ) 47 ACLPublicReadWrite = TBucketACLType(s3cli.CANNED_ACL_PUBLIC_READ_WRITE) 48 ACLUnknown = TBucketACLType("") 49 50 META_HEADER_CACHE_CONTROL = "Cache-Control" 51 META_HEADER_CONTENT_TYPE = "Content-Type" 52 META_HEADER_CONTENT_DISPOSITION = "Content-Disposition" 53 META_HEADER_CONTENT_ENCODING = "Content-Encoding" 54 META_HEADER_CONTENT_LANGUAGE = "Content-Language" 55 META_HEADER_CONTENT_MD5 = "Content-MD5" 56 57 META_HEADER_PREFIX = "X-Yunion-Meta-" 58 ) 59 60 type SBucketStats struct { 61 SizeBytes int64 62 ObjectCount int 63 } 64 65 func (s SBucketStats) Equals(s2 SBucketStats) bool { 66 if s.SizeBytes == s2.SizeBytes && s.ObjectCount == s2.ObjectCount { 67 return true 68 } else { 69 return false 70 } 71 } 72 73 type SBucketAccessUrl struct { 74 Url string 75 Description string 76 Primary bool 77 } 78 79 type SBucketWebsiteRoutingRule struct { 80 ConditionErrorCode string 81 ConditionPrefix string 82 83 RedirectProtocol string 84 RedirectReplaceKey string 85 RedirectReplaceKeyPrefix string 86 } 87 88 type SBucketWebsiteConf struct { 89 // 主页 90 Index string 91 // 错误时返回的文档 92 ErrorDocument string 93 // http或https 94 Protocol string 95 96 Rules []SBucketWebsiteRoutingRule 97 // 网站访问url,一般由bucketid,region等组成 98 Url string 99 } 100 101 type SBucketCORSRule struct { 102 AllowedMethods []string 103 // 允许的源站,可以设为* 104 AllowedOrigins []string 105 AllowedHeaders []string 106 MaxAgeSeconds int 107 ExposeHeaders []string 108 // 规则区别标识 109 Id string 110 } 111 112 type SBucketRefererConf struct { 113 // 域名列表 114 DomainList []string 115 // 域名列表 116 // enmu: Black-List, White-List 117 RefererType string 118 // 是否允许空referer 访问 119 AllowEmptyRefer bool 120 121 Enabled bool 122 } 123 124 type SBucketPolicyStatement struct { 125 // 授权的目标主体 126 Principal map[string][]string `json:"Principal,omitempty"` 127 // 授权的行为 128 Action []string `json:"Action,omitempty"` 129 // Allow|Deny 130 Effect string `json:"Effect,omitempty"` 131 // 被授权的资源 132 Resource []string `json:"Resource,omitempty"` 133 // 触发授权的条件 134 Condition map[string]map[string]interface{} `json:"Condition,omitempty"` 135 136 // 解析字段,主账号id:子账号id 137 PrincipalId []string 138 // map[主账号id:子账号id]子账号名称 139 PrincipalNames map[string]string 140 // Read|ReadWrite|FullControl 141 CannedAction string 142 // 资源路径 143 ResourcePath []string 144 // 根据index 生成 145 Id string 146 } 147 148 type SBucketPolicyStatementInput struct { 149 // 主账号id:子账号id 150 PrincipalId []string 151 // Read|ReadWrite|FullControl 152 CannedAction string 153 // Allow|Deny 154 Effect string 155 // 被授权的资源地址,/* 156 ResourcePath []string 157 // ip 条件 158 IpEquals []string 159 IpNotEquals []string 160 } 161 162 type SBucketMultipartUploads struct { 163 // object name 164 ObjectName string 165 UploadID string 166 // 发起人 167 Initiator string 168 // 发起时间 169 Initiated time.Time 170 } 171 172 type SBaseCloudObject struct { 173 Key string 174 SizeBytes int64 175 StorageClass string 176 ETag string 177 LastModified time.Time 178 Meta http.Header 179 } 180 181 type SListObjectResult struct { 182 Objects []ICloudObject 183 NextMarker string 184 CommonPrefixes []ICloudObject 185 IsTruncated bool 186 } 187 188 type SGetObjectRange struct { 189 Start int64 190 End int64 191 } 192 193 func (r SGetObjectRange) SizeBytes() int64 { 194 return r.End - r.Start + 1 195 } 196 197 var ( 198 rangeExp = regexp.MustCompile(`(bytes=)?(\d*)-(\d*)`) 199 ) 200 201 func ParseRange(rangeStr string) SGetObjectRange { 202 objRange := SGetObjectRange{} 203 if len(rangeStr) > 0 { 204 find := rangeExp.FindAllStringSubmatch(rangeStr, -1) 205 if len(find) > 0 && len(find[0]) > 3 { 206 objRange.Start, _ = strconv.ParseInt(find[0][2], 10, 64) 207 objRange.End, _ = strconv.ParseInt(find[0][3], 10, 64) 208 } 209 } 210 return objRange 211 } 212 213 func (r SGetObjectRange) String() string { 214 if r.Start > 0 && r.End > 0 { 215 return fmt.Sprintf("bytes=%d-%d", r.Start, r.End) 216 } else if r.Start > 0 && r.End <= 0 { 217 return fmt.Sprintf("bytes=%d-", r.Start) 218 } else if r.Start <= 0 && r.End > 0 { 219 return fmt.Sprintf("bytes=0-%d", r.End) 220 } else { 221 return "" 222 } 223 } 224 225 type ICloudBucket interface { 226 IVirtualResource 227 228 MaxPartCount() int 229 MaxPartSizeBytes() int64 230 231 //GetGlobalId() string 232 //GetName() string 233 GetAcl() TBucketACLType 234 GetLocation() string 235 GetIRegion() ICloudRegion 236 GetStorageClass() string 237 GetAccessUrls() []SBucketAccessUrl 238 GetStats() SBucketStats 239 GetLimit() SBucketStats 240 SetLimit(limit SBucketStats) error 241 LimitSupport() SBucketStats 242 243 SetAcl(acl TBucketACLType) error 244 245 ListObjects(prefix string, marker string, delimiter string, maxCount int) (SListObjectResult, error) 246 247 CopyObject(ctx context.Context, destKey string, srcBucket, srcKey string, cannedAcl TBucketACLType, storageClassStr string, meta http.Header) error 248 GetObject(ctx context.Context, key string, rangeOpt *SGetObjectRange) (io.ReadCloser, error) 249 250 DeleteObject(ctx context.Context, keys string) error 251 GetTempUrl(method string, key string, expire time.Duration) (string, error) 252 253 PutObject(ctx context.Context, key string, input io.Reader, sizeBytes int64, cannedAcl TBucketACLType, storageClassStr string, meta http.Header) error 254 255 NewMultipartUpload(ctx context.Context, key string, cannedAcl TBucketACLType, storageClassStr string, meta http.Header) (string, error) 256 UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64, offset, totalSize int64) (string, error) 257 CopyPart(ctx context.Context, key string, uploadId string, partIndex int, srcBucketName string, srcKey string, srcOffset int64, srcLength int64) (string, error) 258 CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error 259 AbortMultipartUpload(ctx context.Context, key string, uploadId string) error 260 261 SetWebsite(conf SBucketWebsiteConf) error 262 GetWebsiteConf() (SBucketWebsiteConf, error) 263 DeleteWebSiteConf() error 264 265 SetCORS(rules []SBucketCORSRule) error 266 GetCORSRules() ([]SBucketCORSRule, error) 267 DeleteCORS() error 268 269 SetReferer(conf SBucketRefererConf) error 270 GetReferer() (SBucketRefererConf, error) 271 272 GetCdnDomains() ([]SCdnDomain, error) 273 274 GetPolicy() ([]SBucketPolicyStatement, error) 275 SetPolicy(policy SBucketPolicyStatementInput) error 276 DeletePolicy(id []string) ([]SBucketPolicyStatement, error) 277 278 ListMultipartUploads() ([]SBucketMultipartUploads, error) 279 } 280 281 type ICloudObject interface { 282 GetIBucket() ICloudBucket 283 284 GetKey() string 285 GetSizeBytes() int64 286 GetLastModified() time.Time 287 GetStorageClass() string 288 GetETag() string 289 290 GetMeta() http.Header 291 SetMeta(ctx context.Context, meta http.Header) error 292 293 GetAcl() TBucketACLType 294 SetAcl(acl TBucketACLType) error 295 } 296 297 type SCloudObject struct { 298 Key string 299 SizeBytes int64 300 StorageClass string 301 ETag string 302 LastModified time.Time 303 Meta http.Header 304 Acl string 305 } 306 307 func ICloudObject2Struct(obj ICloudObject) SCloudObject { 308 return SCloudObject{ 309 Key: obj.GetKey(), 310 SizeBytes: obj.GetSizeBytes(), 311 StorageClass: obj.GetStorageClass(), 312 ETag: obj.GetETag(), 313 LastModified: obj.GetLastModified(), 314 Meta: obj.GetMeta(), 315 Acl: string(obj.GetAcl()), 316 } 317 } 318 319 func ICloudObject2JSONObject(obj ICloudObject) jsonutils.JSONObject { 320 return jsonutils.Marshal(ICloudObject2Struct(obj)) 321 } 322 323 func (o *SBaseCloudObject) GetKey() string { 324 return o.Key 325 } 326 327 func (o *SBaseCloudObject) GetSizeBytes() int64 { 328 return o.SizeBytes 329 } 330 331 func (o *SBaseCloudObject) GetLastModified() time.Time { 332 return o.LastModified 333 } 334 335 func (o *SBaseCloudObject) GetStorageClass() string { 336 return o.StorageClass 337 } 338 339 func (o *SBaseCloudObject) GetETag() string { 340 return o.ETag 341 } 342 343 func (o *SBaseCloudObject) GetMeta() http.Header { 344 return o.Meta 345 } 346 347 //func (o *SBaseCloudObject) SetMeta(meta http.Header) error { 348 // return nil 349 //} 350 351 func GetIBucketById(region ICloudRegion, name string) (ICloudBucket, error) { 352 buckets, err := region.GetIBuckets() 353 if err != nil { 354 return nil, errors.Wrap(err, "region.GetIBuckets") 355 } 356 for i := range buckets { 357 if buckets[i].GetGlobalId() == name { 358 return buckets[i], nil 359 } 360 } 361 return nil, ErrNotFound 362 } 363 364 func GetIBucketByName(region ICloudRegion, name string) (ICloudBucket, error) { 365 buckets, err := region.GetIBuckets() 366 if err != nil { 367 return nil, errors.Wrap(err, "region.GetIBuckets") 368 } 369 for i := range buckets { 370 if buckets[i].GetName() == name { 371 return buckets[i], nil 372 } 373 } 374 return nil, ErrNotFound 375 } 376 377 func GetIBucketStats(bucket ICloudBucket) (SBucketStats, error) { 378 stats := SBucketStats{ 379 ObjectCount: -1, 380 SizeBytes: -1, 381 } 382 objs, err := bucket.ListObjects("", "", "", 1000) 383 if err != nil { 384 return stats, errors.Wrap(err, "GetIObjects") 385 } 386 if objs.IsTruncated { 387 return stats, errors.Wrap(httperrors.ErrTooLarge, "too many objects") 388 } 389 stats.ObjectCount, stats.SizeBytes = 0, 0 390 for _, obj := range objs.Objects { 391 stats.SizeBytes += obj.GetSizeBytes() 392 stats.ObjectCount += 1 393 } 394 return stats, nil 395 } 396 397 type cloudObjectList []ICloudObject 398 399 func (a cloudObjectList) Len() int { return len(a) } 400 func (a cloudObjectList) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 401 func (a cloudObjectList) Less(i, j int) bool { return a[i].GetKey() < a[j].GetKey() } 402 403 func GetPagedObjects(bucket ICloudBucket, objectPrefix string, isRecursive bool, marker string, maxCount int) ([]ICloudObject, string, error) { 404 delimiter := "/" 405 if isRecursive { 406 delimiter = "" 407 } 408 if maxCount > 1000 || maxCount <= 0 { 409 maxCount = 1000 410 } 411 ret := make([]ICloudObject, 0) 412 result, err := bucket.ListObjects(objectPrefix, marker, delimiter, maxCount) 413 if err != nil { 414 return nil, "", errors.Wrap(err, "bucket.ListObjects") 415 } 416 // Send all objects 417 for i := range result.Objects { 418 // if delimited, skip the first object ends with delimiter 419 if !isRecursive && result.Objects[i].GetKey() == objectPrefix && strings.HasSuffix(objectPrefix, delimiter) { 420 continue 421 } 422 ret = append(ret, result.Objects[i]) 423 marker = result.Objects[i].GetKey() 424 } 425 // Send all common prefixes if any. 426 // NOTE: prefixes are only present if the request is delimited. 427 if len(result.CommonPrefixes) > 0 { 428 ret = append(ret, result.CommonPrefixes...) 429 } 430 // sort prefix by name in ascending order 431 sort.Sort(cloudObjectList(ret)) 432 // If next marker present, save it for next request. 433 if result.NextMarker != "" { 434 marker = result.NextMarker 435 } 436 // If not truncated, no more objects 437 if !result.IsTruncated { 438 marker = "" 439 } 440 return ret, marker, nil 441 } 442 443 func GetAllObjects(bucket ICloudBucket, objectPrefix string, isRecursive bool) ([]ICloudObject, error) { 444 ret := make([]ICloudObject, 0) 445 // Save marker for next request. 446 var marker string 447 for { 448 // Get list of objects a maximum of 1000 per request. 449 result, marker, err := GetPagedObjects(bucket, objectPrefix, isRecursive, marker, 1000) 450 if err != nil { 451 return nil, errors.Wrap(err, "bucket.ListObjects") 452 } 453 ret = append(ret, result...) 454 if marker == "" { 455 break 456 } 457 } 458 return ret, nil 459 } 460 461 func GetIObject(bucket ICloudBucket, objectPrefix string) (ICloudObject, error) { 462 tryPrefix := []string{objectPrefix} 463 if strings.HasSuffix(objectPrefix, "/") { 464 tryPrefix = append(tryPrefix, objectPrefix[:len(objectPrefix)-1]) 465 } 466 for _, pref := range tryPrefix { 467 result, err := bucket.ListObjects(pref, "", "", 1) 468 if err != nil { 469 return nil, errors.Wrap(err, "bucket.ListObjects") 470 } 471 objects := result.Objects 472 if len(objects) > 0 && objects[0].GetKey() == objectPrefix { 473 return objects[0], nil 474 } 475 } 476 return nil, ErrNotFound 477 } 478 479 func Makedir(ctx context.Context, bucket ICloudBucket, key string) error { 480 segs := make([]string, 0) 481 for _, seg := range strings.Split(key, "/") { 482 if len(seg) > 0 { 483 segs = append(segs, seg) 484 } 485 } 486 path := strings.Join(segs, "/") + "/" 487 err := bucket.PutObject(ctx, path, strings.NewReader(""), 0, bucket.GetAcl(), "", nil) 488 if err != nil { 489 return errors.Wrap(err, "PutObject") 490 } 491 return nil 492 } 493 494 func UploadObject(ctx context.Context, bucket ICloudBucket, key string, blocksz int64, input io.Reader, sizeBytes int64, cannedAcl TBucketACLType, storageClass string, meta http.Header, debug bool) error { 495 if blocksz <= 0 { 496 blocksz = MAX_PUT_OBJECT_SIZEBYTES 497 } 498 if sizeBytes < blocksz { 499 if debug { 500 log.Debugf("too small, put object in one shot") 501 } 502 return bucket.PutObject(ctx, key, input, sizeBytes, cannedAcl, storageClass, meta) 503 } 504 partSize := blocksz 505 partCount := sizeBytes / partSize 506 if partCount*partSize < sizeBytes { 507 partCount += 1 508 } 509 if partCount > int64(bucket.MaxPartCount()) { 510 partCount = int64(bucket.MaxPartCount()) 511 partSize = sizeBytes / partCount 512 if partSize*partCount < sizeBytes { 513 partSize += 1 514 } 515 if partSize > bucket.MaxPartSizeBytes() { 516 return errors.Error("too larget object") 517 } 518 } 519 if debug { 520 log.Debugf("multipart upload part count %d part size %d", partCount, partSize) 521 } 522 uploadId, err := bucket.NewMultipartUpload(ctx, key, cannedAcl, storageClass, meta) 523 if err != nil { 524 return errors.Wrap(err, "bucket.NewMultipartUpload") 525 } 526 etags := make([]string, partCount) 527 offset := int64(0) 528 for i := 0; i < int(partCount); i += 1 { 529 if i == int(partCount)-1 { 530 partSize = sizeBytes - partSize*(partCount-1) 531 } 532 if debug { 533 log.Debugf("UploadPart %d %d", i+1, partSize) 534 } 535 etag, err := bucket.UploadPart(ctx, key, uploadId, i+1, io.LimitReader(input, partSize), partSize, offset, sizeBytes) 536 if err != nil { 537 err2 := bucket.AbortMultipartUpload(ctx, key, uploadId) 538 if err2 != nil { 539 log.Errorf("bucket.AbortMultipartUpload error %s", err2) 540 } 541 return errors.Wrap(err, "bucket.UploadPart") 542 } 543 offset += partSize 544 etags[i] = etag 545 } 546 err = bucket.CompleteMultipartUpload(ctx, key, uploadId, etags) 547 if err != nil { 548 err2 := bucket.AbortMultipartUpload(ctx, key, uploadId) 549 if err2 != nil { 550 log.Errorf("bucket.AbortMultipartUpload error %s", err2) 551 } 552 return errors.Wrap(err, "CompleteMultipartUpload") 553 } 554 return nil 555 } 556 557 func DeletePrefix(ctx context.Context, bucket ICloudBucket, prefix string) error { 558 objs, err := GetAllObjects(bucket, prefix, true) 559 if err != nil { 560 return errors.Wrap(err, "bucket.GetIObjects") 561 } 562 for i := range objs { 563 err := bucket.DeleteObject(ctx, objs[i].GetKey()) 564 if err != nil { 565 return errors.Wrap(err, "bucket.DeleteObject") 566 } 567 } 568 return nil 569 } 570 571 func MergeMeta(src http.Header, dst http.Header) http.Header { 572 if src != nil && dst != nil { 573 ret := http.Header{} 574 for k, vs := range src { 575 for _, v := range vs { 576 ret.Add(k, v) 577 } 578 } 579 for k, vs := range dst { 580 for _, v := range vs { 581 ret.Add(k, v) 582 } 583 } 584 return ret 585 } else if src != nil && dst == nil { 586 return src 587 } else if src == nil && dst != nil { 588 return dst 589 } else { 590 return nil 591 } 592 } 593 594 func CopyObject(ctx context.Context, blocksz int64, dstBucket ICloudBucket, dstKey string, srcBucket ICloudBucket, srcKey string, dstMeta http.Header, debug bool) error { 595 596 srcObj, err := GetIObject(srcBucket, srcKey) 597 if err != nil { 598 return errors.Wrap(err, "GetIObject") 599 } 600 if blocksz <= 0 { 601 blocksz = MAX_PUT_OBJECT_SIZEBYTES 602 } 603 sizeBytes := srcObj.GetSizeBytes() 604 if sizeBytes < blocksz { 605 if debug { 606 log.Debugf("too small, copy object in one shot") 607 } 608 srcStream, err := srcBucket.GetObject(ctx, srcKey, nil) 609 if err != nil { 610 return errors.Wrap(err, "srcBucket.GetObject") 611 } 612 defer srcStream.Close() 613 err = dstBucket.PutObject(ctx, dstKey, srcStream, sizeBytes, srcObj.GetAcl(), srcObj.GetStorageClass(), MergeMeta(srcObj.GetMeta(), dstMeta)) 614 if err != nil { 615 return errors.Wrap(err, "dstBucket.PutObject") 616 } 617 return nil 618 } 619 partSize := blocksz 620 partCount := sizeBytes / partSize 621 if partCount*partSize < sizeBytes { 622 partCount += 1 623 } 624 if partCount > int64(dstBucket.MaxPartCount()) { 625 partCount = int64(dstBucket.MaxPartCount()) 626 partSize = sizeBytes / partCount 627 if partSize*partCount < sizeBytes { 628 partSize += 1 629 } 630 if partSize > dstBucket.MaxPartSizeBytes() { 631 return errors.Error("too larget object") 632 } 633 } 634 if debug { 635 log.Debugf("multipart upload part count %d part size %d", partCount, partSize) 636 } 637 uploadId, err := dstBucket.NewMultipartUpload(ctx, dstKey, srcObj.GetAcl(), srcObj.GetStorageClass(), MergeMeta(srcObj.GetMeta(), dstMeta)) 638 if err != nil { 639 return errors.Wrap(err, "bucket.NewMultipartUpload") 640 } 641 etags := make([]string, partCount) 642 offset := int64(0) 643 for i := 0; i < int(partCount); i += 1 { 644 start := int64(i) * partSize 645 if i == int(partCount)-1 { 646 partSize = sizeBytes - partSize*(partCount-1) 647 } 648 end := start + partSize - 1 649 rangeOpt := SGetObjectRange{ 650 Start: start, 651 End: end, 652 } 653 if debug { 654 log.Debugf("UploadPart %d %d range: %s (%d)", i+1, partSize, rangeOpt.String(), rangeOpt.SizeBytes()) 655 } 656 srcStream, err := srcBucket.GetObject(ctx, srcKey, &rangeOpt) 657 if err == nil { 658 defer srcStream.Close() 659 var etag string 660 etag, err = dstBucket.UploadPart(ctx, dstKey, uploadId, i+1, io.LimitReader(srcStream, partSize), partSize, offset, sizeBytes) 661 if err == nil { 662 etags[i] = etag 663 continue 664 } 665 } 666 offset += partSize 667 if err != nil { 668 err2 := dstBucket.AbortMultipartUpload(ctx, dstKey, uploadId) 669 if err2 != nil { 670 log.Errorf("bucket.AbortMultipartUpload error %s", err2) 671 } 672 return errors.Wrap(err, "bucket.UploadPart") 673 } 674 } 675 err = dstBucket.CompleteMultipartUpload(ctx, dstKey, uploadId, etags) 676 if err != nil { 677 err2 := dstBucket.AbortMultipartUpload(ctx, dstKey, uploadId) 678 if err2 != nil { 679 log.Errorf("bucket.AbortMultipartUpload error %s", err2) 680 } 681 return errors.Wrap(err, "CompleteMultipartUpload") 682 } 683 return nil 684 } 685 686 func CopyPart(ctx context.Context, 687 iDstBucket ICloudBucket, dstKey string, uploadId string, partNumber int, 688 iSrcBucket ICloudBucket, srcKey string, rangeOpt *SGetObjectRange, 689 ) (string, error) { 690 srcReader, err := iSrcBucket.GetObject(ctx, srcKey, rangeOpt) 691 if err != nil { 692 return "", errors.Wrap(err, "iSrcBucket.GetObject") 693 } 694 defer srcReader.Close() 695 696 etag, err := iDstBucket.UploadPart(ctx, dstKey, uploadId, partNumber, io.LimitReader(srcReader, rangeOpt.SizeBytes()), rangeOpt.SizeBytes(), 0, 0) 697 if err != nil { 698 return "", errors.Wrap(err, "iDstBucket.UploadPart") 699 } 700 return etag, nil 701 } 702 703 func ObjectSetMeta(ctx context.Context, 704 bucket ICloudBucket, obj ICloudObject, 705 meta http.Header, 706 ) error { 707 return bucket.CopyObject(ctx, obj.GetKey(), bucket.GetName(), obj.GetKey(), obj.GetAcl(), obj.GetStorageClass(), meta) 708 } 709 710 func MetaToHttpHeader(metaPrefix string, meta http.Header) http.Header { 711 hdr := http.Header{} 712 for k, v := range meta { 713 if len(v) == 0 || len(v[0]) == 0 { 714 continue 715 } 716 k = http.CanonicalHeaderKey(k) 717 switch k { 718 case META_HEADER_CACHE_CONTROL, 719 META_HEADER_CONTENT_TYPE, 720 META_HEADER_CONTENT_DISPOSITION, 721 META_HEADER_CONTENT_ENCODING, 722 META_HEADER_CONTENT_LANGUAGE, 723 META_HEADER_CONTENT_MD5: 724 hdr.Set(k, v[0]) 725 default: 726 hdr.Set(fmt.Sprintf("%s%s", metaPrefix, k), v[0]) 727 } 728 } 729 return hdr 730 } 731 732 func FetchMetaFromHttpHeader(metaPrefix string, headers http.Header) http.Header { 733 metaPrefix = http.CanonicalHeaderKey(metaPrefix) 734 meta := http.Header{} 735 for hdr, vals := range headers { 736 hdr = http.CanonicalHeaderKey(hdr) 737 if strings.HasPrefix(hdr, metaPrefix) { 738 for _, val := range vals { 739 meta.Add(hdr[len(metaPrefix):], val) 740 } 741 } 742 } 743 for _, hdr := range []string{ 744 META_HEADER_CONTENT_TYPE, 745 META_HEADER_CONTENT_ENCODING, 746 META_HEADER_CONTENT_DISPOSITION, 747 META_HEADER_CONTENT_LANGUAGE, 748 META_HEADER_CACHE_CONTROL, 749 } { 750 val := headers.Get(hdr) 751 if len(val) > 0 { 752 meta.Set(hdr, val) 753 } 754 } 755 return meta 756 } 757 758 func SetBucketCORS(ibucket ICloudBucket, rules []SBucketCORSRule) error { 759 if len(rules) == 0 { 760 return nil 761 } 762 763 oldRules, err := ibucket.GetCORSRules() 764 if err != nil { 765 return errors.Wrap(err, "ibucket.GetCORSRules()") 766 } 767 768 newSet := []SBucketCORSRule{} 769 updateSet := map[int]SBucketCORSRule{} 770 for i := range rules { 771 index, err := strconv.Atoi(rules[i].Id) 772 if err == nil && index < len(oldRules) { 773 updateSet[index] = rules[i] 774 } else { 775 newSet = append(newSet, rules[i]) 776 } 777 } 778 779 updatedRules := []SBucketCORSRule{} 780 for i := range oldRules { 781 if _, ok := updateSet[i]; !ok { 782 updatedRules = append(updatedRules, oldRules[i]) 783 } else { 784 updatedRules = append(updatedRules, updateSet[i]) 785 } 786 } 787 updatedRules = append(updatedRules, newSet...) 788 789 err = ibucket.SetCORS(updatedRules) 790 if err != nil { 791 return errors.Wrap(err, "ibucket.SetCORS(updatedRules)") 792 } 793 return nil 794 } 795 796 func DeleteBucketCORS(ibucket ICloudBucket, id []string) ([]SBucketCORSRule, error) { 797 if len(id) == 0 { 798 return nil, nil 799 } 800 deletedRules := []SBucketCORSRule{} 801 802 oldRules, err := ibucket.GetCORSRules() 803 if err != nil { 804 return nil, errors.Wrap(err, "ibucket.GetCORSRules()") 805 } 806 807 excludeMap := map[int]bool{} 808 for i := range id { 809 index, err := strconv.Atoi(id[i]) 810 if err == nil && index < len(oldRules) { 811 excludeMap[index] = true 812 } 813 } 814 if len(excludeMap) == 0 { 815 return nil, nil 816 } 817 818 newRules := []SBucketCORSRule{} 819 for i := range oldRules { 820 if _, ok := excludeMap[i]; !ok { 821 newRules = append(newRules, oldRules[i]) 822 } else { 823 deletedRules = append(deletedRules, oldRules[i]) 824 } 825 } 826 827 if len(newRules) == 0 { 828 err = ibucket.DeleteCORS() 829 if err != nil { 830 return nil, errors.Wrapf(err, "ibucket.DeleteCORS()") 831 } 832 } else { 833 err = ibucket.SetCORS(newRules) 834 if err != nil { 835 return nil, errors.Wrapf(err, "ibucket.SetBucketCORS(newRules)") 836 } 837 } 838 839 return deletedRules, nil 840 } 841 842 func SetBucketTags(ctx context.Context, iBucket ICloudBucket, mangerId string, tags map[string]string) (TagsUpdateInfo, error) { 843 ret := TagsUpdateInfo{} 844 old, err := iBucket.GetTags() 845 if err != nil { 846 if errors.Cause(err) == ErrNotImplemented || errors.Cause(err) == ErrNotSupported { 847 return ret, nil 848 } 849 return ret, errors.Wrapf(err, "iBucket.GetTags") 850 } 851 ret.OldTags, ret.NewTags = old, tags 852 if !ret.IsChanged() { 853 return ret, nil 854 } 855 return ret, SetTags(ctx, iBucket, mangerId, tags, true) 856 }