yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/bucket.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 qcloud 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "strconv" 23 "strings" 24 "time" 25 26 "github.com/tencentyun/cos-go-sdk-v5" 27 "gopkg.in/fatih/set.v0" 28 29 "yunion.io/x/jsonutils" 30 "yunion.io/x/log" 31 "yunion.io/x/pkg/errors" 32 "yunion.io/x/pkg/util/timeutils" 33 "yunion.io/x/s3cli" 34 35 api "yunion.io/x/cloudmux/pkg/apis/compute" 36 "yunion.io/x/cloudmux/pkg/cloudprovider" 37 "yunion.io/x/cloudmux/pkg/multicloud" 38 ) 39 40 const ( 41 COS_META_HEADER = "X-Cos-Meta-" 42 ) 43 44 type SBucket struct { 45 multicloud.SBaseBucket 46 QcloudTags 47 48 appId string 49 50 region *SRegion 51 zone *SZone 52 53 Name string 54 Location string 55 CreateDate time.Time 56 } 57 58 func (b *SBucket) GetProjectId() string { 59 return "" 60 } 61 62 func (b *SBucket) GetGlobalId() string { 63 if b.getAppId() == b.region.client.appId { 64 return b.Name 65 } else { 66 return b.getFullName() 67 } 68 } 69 70 func (b *SBucket) GetName() string { 71 return b.GetGlobalId() 72 } 73 74 func (b *SBucket) GetLocation() string { 75 return b.Location 76 } 77 78 func (b *SBucket) GetIRegion() cloudprovider.ICloudRegion { 79 return b.region 80 } 81 82 func (b *SBucket) GetCreatedAt() time.Time { 83 return b.CreateDate 84 } 85 86 func (b *SBucket) GetStorageClass() string { 87 return "" 88 } 89 90 const ( 91 ACL_GROUP_URI_ALL_USERS = "http://cam.qcloud.com/groups/global/AllUsers" 92 ACL_GROUP_URI_AUTH_USERS = "http://cam.qcloud.com/groups/global/AuthenticatedUsers" 93 ) 94 95 func cosAcl2CannedAcl(acls []cos.ACLGrant) cloudprovider.TBucketACLType { 96 switch { 97 case len(acls) == 1: 98 if acls[0].Grantee.URI == "" && acls[0].Permission == s3cli.PERMISSION_FULL_CONTROL { 99 return cloudprovider.ACLPrivate 100 } 101 case len(acls) == 2: 102 for _, g := range acls { 103 if g.Grantee.URI == ACL_GROUP_URI_AUTH_USERS && g.Permission == s3cli.PERMISSION_READ { 104 return cloudprovider.ACLAuthRead 105 } 106 if g.Grantee.URI == ACL_GROUP_URI_ALL_USERS && g.Permission == s3cli.PERMISSION_READ { 107 return cloudprovider.ACLPublicRead 108 } 109 } 110 case len(acls) == 3: 111 for _, g := range acls { 112 if g.Grantee.URI == ACL_GROUP_URI_ALL_USERS && g.Permission == s3cli.PERMISSION_WRITE { 113 return cloudprovider.ACLPublicReadWrite 114 } 115 } 116 } 117 return cloudprovider.ACLUnknown 118 } 119 120 func (b *SBucket) GetAcl() cloudprovider.TBucketACLType { 121 acl := cloudprovider.ACLPrivate 122 coscli, err := b.region.GetCosClient(b) 123 if err != nil { 124 log.Errorf("GetCosClient fail %s", err) 125 return acl 126 } 127 result, _, err := coscli.Bucket.GetACL(context.Background()) 128 if err != nil { 129 log.Errorf("coscli.Bucket.GetACL fail %s", err) 130 return acl 131 } 132 return cosAcl2CannedAcl(result.AccessControlList) 133 } 134 135 func (b *SBucket) SetAcl(aclStr cloudprovider.TBucketACLType) error { 136 coscli, err := b.region.GetCosClient(b) 137 if err != nil { 138 return errors.Wrap(err, "b.region.GetCosClient") 139 } 140 opts := &cos.BucketPutACLOptions{} 141 opts.Header = &cos.ACLHeaderOptions{} 142 opts.Header.XCosACL = string(aclStr) 143 _, err = coscli.Bucket.PutACL(context.Background(), opts) 144 if err != nil { 145 return errors.Wrap(err, "PutACL") 146 } 147 return nil 148 } 149 150 func (b *SBucket) getAppId() string { 151 if len(b.appId) > 0 { 152 return b.appId 153 } 154 if b.zone != nil { 155 return b.zone.region.client.appId 156 } 157 return b.region.client.appId 158 } 159 160 func (b *SBucket) getFullName() string { 161 return fmt.Sprintf("%s-%s", b.Name, b.getAppId()) 162 } 163 164 func (b *SBucket) getBucketUrlHost() string { 165 if b.zone != nil { 166 return fmt.Sprintf("%s.%s", b.getFullName(), b.zone.getCosEndpoint()) 167 } else { 168 return fmt.Sprintf("%s.%s", b.getFullName(), b.region.getCosEndpoint()) 169 } 170 } 171 172 func (b *SBucket) getBucketUrl() string { 173 return fmt.Sprintf("https://%s", b.getBucketUrlHost()) 174 } 175 176 func (b *SBucket) getBucketWebsiteUrlHost() string { 177 if b.zone != nil { 178 return fmt.Sprintf("%s.%s", b.getFullName(), b.zone.getCosWebsiteEndpoint()) 179 } else { 180 return fmt.Sprintf("%s.%s", b.getFullName(), b.region.getCosWebsiteEndpoint()) 181 } 182 } 183 184 func (b *SBucket) getWebsiteUrl() string { 185 return fmt.Sprintf("https://%s", b.getBucketWebsiteUrlHost()) 186 } 187 188 func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl { 189 return []cloudprovider.SBucketAccessUrl{ 190 { 191 Url: b.getBucketUrl(), 192 Description: "bucket domain", 193 Primary: true, 194 }, 195 { 196 Url: fmt.Sprintf("https://%s/%s", b.region.getCosEndpoint(), b.getFullName()), 197 Description: "cos domain", 198 }, 199 } 200 } 201 202 func (b *SBucket) GetStats() cloudprovider.SBucketStats { 203 stats, _ := cloudprovider.GetIBucketStats(b) 204 return stats 205 } 206 207 func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) { 208 result := cloudprovider.SListObjectResult{} 209 coscli, err := b.region.GetCosClient(b) 210 if err != nil { 211 return result, errors.Wrap(err, "GetCosClient") 212 } 213 opts := &cos.BucketGetOptions{} 214 if len(prefix) > 0 { 215 opts.Prefix = prefix 216 } 217 if len(marker) > 0 { 218 opts.Marker = marker 219 } 220 if len(delimiter) > 0 { 221 opts.Delimiter = delimiter 222 } 223 if maxCount > 0 { 224 opts.MaxKeys = maxCount 225 } 226 oResult, _, err := coscli.Bucket.Get(context.Background(), opts) 227 if err != nil { 228 return result, errors.Wrap(err, "coscli.Bucket.Get") 229 } 230 result.Objects = make([]cloudprovider.ICloudObject, 0) 231 for _, object := range oResult.Contents { 232 lastModified, _ := timeutils.ParseTimeStr(object.LastModified) 233 obj := &SObject{ 234 bucket: b, 235 SBaseCloudObject: cloudprovider.SBaseCloudObject{ 236 StorageClass: string(object.StorageClass), 237 Key: object.Key, 238 SizeBytes: int64(object.Size), 239 ETag: object.ETag, 240 LastModified: lastModified, 241 }, 242 } 243 result.Objects = append(result.Objects, obj) 244 } 245 if oResult.CommonPrefixes != nil { 246 result.CommonPrefixes = make([]cloudprovider.ICloudObject, 0) 247 for _, commPrefix := range oResult.CommonPrefixes { 248 obj := &SObject{ 249 bucket: b, 250 SBaseCloudObject: cloudprovider.SBaseCloudObject{ 251 Key: commPrefix, 252 }, 253 } 254 result.CommonPrefixes = append(result.CommonPrefixes, obj) 255 } 256 } 257 result.IsTruncated = oResult.IsTruncated 258 result.NextMarker = oResult.NextMarker 259 return result, nil 260 } 261 262 func (b *SBucket) PutObject(ctx context.Context, key string, reader io.Reader, sizeBytes int64, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { 263 coscli, err := b.region.GetCosClient(b) 264 if err != nil { 265 return errors.Wrap(err, "GetCosClient") 266 } 267 opts := &cos.ObjectPutOptions{ 268 ACLHeaderOptions: &cos.ACLHeaderOptions{}, 269 ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{}, 270 } 271 if sizeBytes > 0 { 272 opts.ContentLength = sizeBytes 273 } 274 if meta != nil { 275 extraHdr := http.Header{} 276 for k, v := range meta { 277 if len(v) == 0 || len(v[0]) == 0 { 278 continue 279 } 280 switch http.CanonicalHeaderKey(k) { 281 case cloudprovider.META_HEADER_CACHE_CONTROL: 282 opts.CacheControl = v[0] 283 case cloudprovider.META_HEADER_CONTENT_TYPE: 284 opts.ContentType = v[0] 285 case cloudprovider.META_HEADER_CONTENT_MD5: 286 opts.ContentMD5 = v[0] 287 case cloudprovider.META_HEADER_CONTENT_ENCODING: 288 opts.ContentEncoding = v[0] 289 case cloudprovider.META_HEADER_CONTENT_DISPOSITION: 290 opts.ContentDisposition = v[0] 291 default: 292 extraHdr.Add(fmt.Sprintf("%s%s", COS_META_HEADER, k), v[0]) 293 } 294 } 295 if len(extraHdr) > 0 { 296 opts.XCosMetaXXX = &extraHdr 297 } 298 } 299 if len(cannedAcl) == 0 { 300 cannedAcl = b.GetAcl() 301 } 302 opts.XCosACL = string(cannedAcl) 303 if len(storageClassStr) > 0 { 304 opts.XCosStorageClass = storageClassStr 305 } 306 _, err = coscli.Object.Put(ctx, key, reader, opts) 307 if err != nil { 308 return errors.Wrap(err, "coscli.Object.Put") 309 } 310 return nil 311 } 312 313 func (b *SBucket) NewMultipartUpload(ctx context.Context, key string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) (string, error) { 314 coscli, err := b.region.GetCosClient(b) 315 if err != nil { 316 return "", errors.Wrap(err, "GetCosClient") 317 } 318 opts := &cos.InitiateMultipartUploadOptions{ 319 ACLHeaderOptions: &cos.ACLHeaderOptions{}, 320 ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{}, 321 } 322 if meta != nil { 323 extraHdr := http.Header{} 324 for k, v := range meta { 325 if len(v) == 0 || len(v[0]) == 0 { 326 continue 327 } 328 switch http.CanonicalHeaderKey(k) { 329 case cloudprovider.META_HEADER_CACHE_CONTROL: 330 opts.CacheControl = v[0] 331 case cloudprovider.META_HEADER_CONTENT_TYPE: 332 opts.ContentType = v[0] 333 case cloudprovider.META_HEADER_CONTENT_MD5: 334 opts.ContentMD5 = v[0] 335 case cloudprovider.META_HEADER_CONTENT_ENCODING: 336 opts.ContentEncoding = v[0] 337 case cloudprovider.META_HEADER_CONTENT_DISPOSITION: 338 opts.ContentDisposition = v[0] 339 default: 340 extraHdr.Add(fmt.Sprintf("%s%s", COS_META_HEADER, k), v[0]) 341 } 342 } 343 if len(extraHdr) > 0 { 344 opts.XCosMetaXXX = &extraHdr 345 } 346 } 347 if len(cannedAcl) == 0 { 348 cannedAcl = b.GetAcl() 349 } 350 opts.XCosACL = string(cannedAcl) 351 if len(storageClassStr) > 0 { 352 opts.XCosStorageClass = storageClassStr 353 } 354 result, _, err := coscli.Object.InitiateMultipartUpload(ctx, key, opts) 355 if err != nil { 356 return "", errors.Wrap(err, "InitiateMultipartUpload") 357 } 358 359 return result.UploadID, nil 360 } 361 362 func (b *SBucket) UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64, offset, totalSize int64) (string, error) { 363 coscli, err := b.region.GetCosClient(b) 364 if err != nil { 365 return "", errors.Wrap(err, "GetCosClient") 366 } 367 opts := &cos.ObjectUploadPartOptions{} 368 opts.ContentLength = partSize 369 resp, err := coscli.Object.UploadPart(ctx, key, uploadId, partIndex, input, opts) 370 if err != nil { 371 return "", errors.Wrap(err, "UploadPart") 372 } 373 374 return resp.Header.Get("Etag"), nil 375 } 376 377 func (b *SBucket) CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error { 378 coscli, err := b.region.GetCosClient(b) 379 if err != nil { 380 return errors.Wrap(err, "GetCosClient") 381 } 382 opts := &cos.CompleteMultipartUploadOptions{} 383 parts := make([]cos.Object, len(partEtags)) 384 for i := range partEtags { 385 parts[i] = cos.Object{ 386 PartNumber: i + 1, 387 ETag: partEtags[i], 388 } 389 } 390 opts.Parts = parts 391 _, _, err = coscli.Object.CompleteMultipartUpload(ctx, key, uploadId, opts) 392 393 if err != nil { 394 return errors.Wrap(err, "CompleteMultipartUpload") 395 } 396 397 return nil 398 } 399 400 func (b *SBucket) AbortMultipartUpload(ctx context.Context, key string, uploadId string) error { 401 coscli, err := b.region.GetCosClient(b) 402 if err != nil { 403 return errors.Wrap(err, "GetCosClient") 404 } 405 406 _, err = coscli.Object.AbortMultipartUpload(ctx, key, uploadId) 407 if err != nil { 408 return errors.Wrap(err, "AbortMultipartUpload") 409 } 410 411 return nil 412 } 413 414 func (b *SBucket) DeleteObject(ctx context.Context, key string) error { 415 coscli, err := b.region.GetCosClient(b) 416 if err != nil { 417 return errors.Wrap(err, "GetCosClient") 418 } 419 _, err = coscli.Object.Delete(ctx, key) 420 if err != nil { 421 return errors.Wrap(err, "coscli.Object.Delete") 422 } 423 return nil 424 } 425 426 func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) { 427 if method != "GET" && method != "PUT" && method != "DELETE" { 428 return "", errors.Error("unsupported method") 429 } 430 coscli, err := b.region.GetCosClient(b) 431 if err != nil { 432 return "", errors.Wrap(err, "GetCosClient") 433 } 434 url, err := coscli.Object.GetPresignedURL(context.Background(), method, key, 435 b.region.client.secretId, 436 b.region.client.secretKey, 437 expire, nil) 438 if err != nil { 439 return "", errors.Wrap(err, "coscli.Object.GetPresignedURL") 440 } 441 return url.String(), nil 442 } 443 444 func (b *SBucket) CopyObject(ctx context.Context, destKey string, srcBucketName, srcKey string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { 445 coscli, err := b.region.GetCosClient(b) 446 if err != nil { 447 return errors.Wrap(err, "GetCosClient") 448 } 449 opts := &cos.ObjectCopyOptions{ 450 ObjectCopyHeaderOptions: &cos.ObjectCopyHeaderOptions{}, 451 ACLHeaderOptions: &cos.ACLHeaderOptions{}, 452 } 453 if len(cannedAcl) == 0 { 454 cannedAcl = b.GetAcl() 455 } 456 opts.XCosACL = string(cannedAcl) 457 if len(storageClassStr) > 0 { 458 opts.XCosStorageClass = storageClassStr 459 } 460 if meta != nil { 461 opts.XCosMetadataDirective = "Replaced" 462 extraHdr := http.Header{} 463 for k, v := range meta { 464 if len(v) == 0 || len(v[0]) == 0 { 465 continue 466 } 467 switch http.CanonicalHeaderKey(k) { 468 case cloudprovider.META_HEADER_CACHE_CONTROL: 469 opts.CacheControl = v[0] 470 case cloudprovider.META_HEADER_CONTENT_TYPE: 471 opts.ContentType = v[0] 472 case cloudprovider.META_HEADER_CONTENT_ENCODING: 473 opts.ContentEncoding = v[0] 474 case cloudprovider.META_HEADER_CONTENT_DISPOSITION: 475 opts.ContentDisposition = v[0] 476 default: 477 extraHdr.Add(fmt.Sprintf("%s%s", COS_META_HEADER, k), v[0]) 478 } 479 } 480 if len(extraHdr) > 0 { 481 opts.XCosMetaXXX = &extraHdr 482 } 483 } else { 484 opts.XCosMetadataDirective = "Copy" 485 } 486 srcBucket := SBucket{ 487 region: b.region, 488 Name: srcBucketName, 489 } 490 srcUrl := fmt.Sprintf("%s/%s", srcBucket.getBucketUrlHost(), srcKey) 491 _, _, err = coscli.Object.Copy(ctx, destKey, srcUrl, opts) 492 if err != nil { 493 return errors.Wrap(err, "coscli.Object.Copy") 494 } 495 return nil 496 } 497 498 func (b *SBucket) GetObject(ctx context.Context, key string, rangeOpt *cloudprovider.SGetObjectRange) (io.ReadCloser, error) { 499 coscli, err := b.region.GetCosClient(b) 500 if err != nil { 501 return nil, errors.Wrap(err, "GetCosClient") 502 } 503 opts := &cos.ObjectGetOptions{} 504 if rangeOpt != nil { 505 opts.Range = rangeOpt.String() 506 } 507 resp, err := coscli.Object.Get(ctx, key, opts) 508 if err != nil { 509 return nil, errors.Wrap(err, "coscli.Object.Get") 510 } 511 return resp.Body, nil 512 } 513 514 func (b *SBucket) CopyPart(ctx context.Context, key string, uploadId string, partIndex int, srcBucketName string, srcKey string, srcOffset int64, srcLength int64) (string, error) { 515 coscli, err := b.region.GetCosClient(b) 516 if err != nil { 517 return "", errors.Wrap(err, "GetCosClient") 518 } 519 srcBucket := SBucket{ 520 region: b.region, 521 Name: srcBucketName, 522 } 523 opts := cos.ObjectCopyPartOptions{} 524 srcUrl := fmt.Sprintf("%s/%s", srcBucket.getBucketUrlHost(), srcKey) 525 opts.XCosCopySourceRange = fmt.Sprintf("bytes=%d-%d", srcOffset, srcOffset+srcLength-1) 526 result, _, err := coscli.Object.CopyPart(ctx, key, uploadId, partIndex, srcUrl, &opts) 527 if err != nil { 528 return "", errors.Wrap(err, "coscli.Object.CopyPart") 529 } 530 return result.ETag, nil 531 } 532 533 func (b *SBucket) SetWebsite(websitConf cloudprovider.SBucketWebsiteConf) error { 534 if len(websitConf.Index) == 0 { 535 return errors.Wrap(cloudprovider.ErrNotSupported, "missing Index") 536 } 537 if len(websitConf.ErrorDocument) == 0 { 538 return errors.Wrap(cloudprovider.ErrNotSupported, "missing ErrorDocument") 539 } 540 if websitConf.Protocol != "http" && websitConf.Protocol != "https" { 541 return errors.Wrap(cloudprovider.ErrNotSupported, "missing Protocol") 542 } 543 544 coscli, err := b.region.GetCosClient(b) 545 if err != nil { 546 return errors.Wrap(err, "b.region.GetCosClient") 547 } 548 549 rulesOpts := []cos.WebsiteRoutingRule{} 550 for i := range websitConf.Rules { 551 rulesOpts = append(rulesOpts, cos.WebsiteRoutingRule{ 552 ConditionErrorCode: websitConf.Rules[i].ConditionErrorCode, 553 ConditionPrefix: websitConf.Rules[i].ConditionPrefix, 554 555 RedirectProtocol: websitConf.Rules[i].RedirectProtocol, 556 RedirectReplaceKey: websitConf.Rules[i].RedirectReplaceKey, 557 RedirectReplaceKeyPrefix: websitConf.Rules[i].ConditionPrefix, 558 }) 559 } 560 opts := &cos.BucketPutWebsiteOptions{ 561 Index: websitConf.Index, 562 Error: &cos.ErrorDocument{Key: websitConf.ErrorDocument}, 563 RedirectProtocol: &cos.RedirectRequestsProtocol{Protocol: websitConf.Protocol}, 564 } 565 if len(rulesOpts) > 0 { 566 opts.RoutingRules = &cos.WebsiteRoutingRules{Rules: rulesOpts} 567 } 568 569 _, err = coscli.Bucket.PutWebsite(context.Background(), opts) 570 if err != nil { 571 return errors.Wrap(err, "PutWebsite") 572 } 573 return nil 574 } 575 576 func (b *SBucket) GetWebsiteConf() (cloudprovider.SBucketWebsiteConf, error) { 577 coscli, err := b.region.GetCosClient(b) 578 if err != nil { 579 return cloudprovider.SBucketWebsiteConf{}, errors.Wrap(err, "b.region.GetCosClient") 580 } 581 websiteResult, _, err := coscli.Bucket.GetWebsite(context.Background()) 582 if err != nil { 583 if strings.Contains(err.Error(), "NoSuchWebsiteConfiguration") { 584 return cloudprovider.SBucketWebsiteConf{}, nil 585 } 586 return cloudprovider.SBucketWebsiteConf{}, errors.Wrap(err, "coscli.Bucket.GetWebsite") 587 } 588 589 result := cloudprovider.SBucketWebsiteConf{ 590 Index: websiteResult.Index, 591 } 592 if websiteResult.Error != nil { 593 result.ErrorDocument = websiteResult.Error.Key 594 } 595 if websiteResult.RedirectProtocol != nil { 596 result.Protocol = websiteResult.RedirectProtocol.Protocol 597 } 598 routingRules := []cloudprovider.SBucketWebsiteRoutingRule{} 599 if websiteResult.RoutingRules != nil { 600 for i := range websiteResult.RoutingRules.Rules { 601 routingRules = append(routingRules, cloudprovider.SBucketWebsiteRoutingRule{ 602 ConditionErrorCode: websiteResult.RoutingRules.Rules[i].ConditionErrorCode, 603 ConditionPrefix: websiteResult.RoutingRules.Rules[i].ConditionPrefix, 604 605 RedirectProtocol: websiteResult.RoutingRules.Rules[i].RedirectProtocol, 606 RedirectReplaceKey: websiteResult.RoutingRules.Rules[i].RedirectReplaceKey, 607 RedirectReplaceKeyPrefix: websiteResult.RoutingRules.Rules[i].RedirectReplaceKeyPrefix, 608 }) 609 } 610 } 611 result.Rules = routingRules 612 result.Url = b.getWebsiteUrl() 613 return result, nil 614 } 615 616 func (b *SBucket) DeleteWebSiteConf() error { 617 coscli, err := b.region.GetCosClient(b) 618 if err != nil { 619 return errors.Wrap(err, "b.region.GetCosClient") 620 } 621 _, err = coscli.Bucket.DeleteWebsite(context.Background()) 622 if err != nil { 623 return errors.Wrap(err, "coscli.Bucket.DeleteWebsite") 624 } 625 return nil 626 } 627 628 func (b *SBucket) SetCORS(rules []cloudprovider.SBucketCORSRule) error { 629 if len(rules) == 0 { 630 return nil 631 } 632 coscli, err := b.region.GetCosClient(b) 633 if err != nil { 634 return errors.Wrap(err, "b.region.GetCosClient") 635 } 636 input := cos.BucketPutCORSOptions{} 637 for i := range rules { 638 input.Rules = append(input.Rules, cos.BucketCORSRule{ 639 AllowedOrigins: rules[i].AllowedOrigins, 640 AllowedMethods: rules[i].AllowedMethods, 641 AllowedHeaders: rules[i].AllowedHeaders, 642 MaxAgeSeconds: rules[i].MaxAgeSeconds, 643 ExposeHeaders: rules[i].ExposeHeaders, 644 ID: rules[i].Id, 645 }) 646 } 647 648 _, err = coscli.Bucket.PutCORS(context.Background(), &input) 649 if err != nil { 650 return errors.Wrap(err, "coscli.Bucket.PutCORS") 651 } 652 return nil 653 } 654 655 func (b *SBucket) GetCORSRules() ([]cloudprovider.SBucketCORSRule, error) { 656 coscli, err := b.region.GetCosClient(b) 657 if err != nil { 658 return nil, errors.Wrap(err, "b.region.GetCosClient") 659 } 660 conf, _, err := coscli.Bucket.GetCORS(context.Background()) 661 if err != nil { 662 if strings.Contains(err.Error(), "NoSuchCORSConfiguration") { 663 return nil, nil 664 } 665 return nil, errors.Wrap(err, "b.region.GetCORS") 666 } 667 result := []cloudprovider.SBucketCORSRule{} 668 for i := range conf.Rules { 669 result = append(result, cloudprovider.SBucketCORSRule{ 670 AllowedOrigins: conf.Rules[i].AllowedOrigins, 671 AllowedMethods: conf.Rules[i].AllowedMethods, 672 AllowedHeaders: conf.Rules[i].AllowedHeaders, 673 MaxAgeSeconds: conf.Rules[i].MaxAgeSeconds, 674 ExposeHeaders: conf.Rules[i].ExposeHeaders, 675 Id: strconv.Itoa(i), 676 }) 677 } 678 return result, nil 679 } 680 681 func (b *SBucket) DeleteCORS() error { 682 coscli, err := b.region.GetCosClient(b) 683 if err != nil { 684 return errors.Wrap(err, "b.region.GetCosClient") 685 } 686 _, err = coscli.Bucket.DeleteCORS(context.Background()) 687 if err != nil { 688 return errors.Wrap(err, "coscli.Bucket.DeleteCORS") 689 } 690 return nil 691 } 692 693 func (b *SBucket) SetReferer(conf cloudprovider.SBucketRefererConf) error { 694 coscli, err := b.region.GetCosClient(b) 695 if err != nil { 696 return errors.Wrap(err, "b.region.GetCosClient") 697 } 698 699 if !conf.Enabled { 700 _, err = coscli.Bucket.PutReferer(context.Background(), nil) 701 return errors.Wrap(err, "Disable Refer") 702 } 703 704 opts := cos.BucketPutRefererOptions{ 705 Status: "Enabled", 706 EmptyReferConfiguration: "Deny", 707 RefererType: conf.RefererType, 708 DomainList: conf.DomainList, 709 } 710 711 if conf.AllowEmptyRefer { 712 opts.EmptyReferConfiguration = "Allow" 713 } 714 715 _, err = coscli.Bucket.PutReferer(context.Background(), &opts) 716 if err != nil { 717 return errors.Wrap(err, "coscli.Bucket.PutReferer") 718 } 719 return nil 720 } 721 func (b *SBucket) GetReferer() (cloudprovider.SBucketRefererConf, error) { 722 result := cloudprovider.SBucketRefererConf{} 723 coscli, err := b.region.GetCosClient(b) 724 if err != nil { 725 return result, errors.Wrap(err, "b.region.GetCosClient") 726 } 727 728 referResult, _, err := coscli.Bucket.GetReferer(context.Background()) 729 if err != nil { 730 return result, errors.Wrap(err, " coscli.Bucket.GetReferer") 731 } 732 733 result.AllowEmptyRefer = (referResult.EmptyReferConfiguration == "Allow") 734 result.Enabled = (referResult.Status == "Enabled") 735 result.RefererType = referResult.RefererType 736 result.DomainList = referResult.DomainList 737 738 return result, nil 739 } 740 741 func toAPICdnArea(area string) string { 742 switch area { 743 case "mainland": 744 return api.CDN_DOMAIN_AREA_MAINLAND 745 case "overseas": 746 return api.CDN_DOMAIN_AREA_OVERSEAS 747 case "global": 748 return api.CDN_DOMAIN_AREA_GLOBAL 749 default: 750 return "" 751 } 752 } 753 func toAPICdnStatus(status string) string { 754 switch status { 755 case "online": 756 return api.CDN_DOMAIN_STATUS_ONLINE 757 case "offline": 758 return api.CDN_DOMAIN_STATUS_OFFLINE 759 case "processing": 760 return api.CDN_DOMAIN_STATUS_PROCESSING 761 case "rejected": 762 return api.CDN_DOMAIN_STATUS_REJECTED 763 default: 764 return "" 765 } 766 } 767 768 func (b *SBucket) GetCdnDomains() ([]cloudprovider.SCdnDomain, error) { 769 result := []cloudprovider.SCdnDomain{} 770 bucketHost := b.getBucketUrlHost() 771 bucketWebsiteHost := b.getBucketWebsiteUrlHost() 772 773 bucketCdnDomains, err := b.region.client.DescribeAllCdnDomains(nil, []string{bucketHost}, "cos") 774 if err != nil { 775 return nil, errors.Wrapf(err, `b.region.client.DescribeAllCdnDomains(nil, []string{%s}, "cos")`, bucketHost) 776 } 777 778 for i := range bucketCdnDomains { 779 result = append(result, cloudprovider.SCdnDomain{ 780 Domain: bucketCdnDomains[i].Domain, 781 Status: toAPICdnStatus(bucketCdnDomains[i].Status), 782 Cname: bucketCdnDomains[i].Cname, 783 Area: toAPICdnArea(bucketCdnDomains[i].Area), 784 Origin: bucketHost, 785 OriginType: api.CDN_DOMAIN_ORIGIN_TYPE_BUCKET, 786 }) 787 } 788 789 bucketWebsiteCdnDomains, err := b.region.client.DescribeAllCdnDomains(nil, []string{bucketWebsiteHost}, "cos") 790 if err != nil { 791 return nil, errors.Wrapf(err, `b.region.client.DescribeAllCdnDomains(nil, []string{%s}, "cos")`, bucketWebsiteHost) 792 } 793 794 for i := range bucketWebsiteCdnDomains { 795 result = append(result, cloudprovider.SCdnDomain{ 796 Domain: bucketWebsiteCdnDomains[i].Domain, 797 Status: toAPICdnStatus(bucketWebsiteCdnDomains[i].Status), 798 Cname: bucketWebsiteCdnDomains[i].Cname, 799 Area: toAPICdnArea(bucketWebsiteCdnDomains[i].Area), 800 Origin: bucketWebsiteHost, 801 OriginType: api.CDN_DOMAIN_ORIGIN_TYPE_BUCKET, 802 }) 803 } 804 return result, nil 805 } 806 807 func getQcsResourcePath(resource []string) []string { 808 path := []string{} 809 for i := range resource { 810 strs := strings.Split(resource[i], ":") 811 path = append(path, strs[len(strs)-1]) 812 } 813 return path 814 } 815 816 func getQcsUserId(principal []string) []string { 817 ids := []string{} 818 for i := range principal { 819 // qcs::cam::uin/100008182714:uin/100008182714 820 // qcs::cam::uin/100008182714:service/cdn 821 // qcs::cam::anyone:anyone 822 823 strs := strings.Split(principal[i], "::") 824 ids = append(ids, strings.Replace(strs[len(strs)-1], "uin/", "", 2)) 825 } 826 827 return ids 828 } 829 830 var cannedReadActions = [...]string{ 831 "name/cos:GetBucket", 832 "name/cos:GetBucketObjectVersions", 833 "name/cos:HeadBucket", 834 "name/cos:ListMultipartUploads", 835 "name/cos:ListParts", 836 "name/cos:GetObject", 837 "name/cos:HeadObject", 838 "name/cos:OptionsObject", 839 } 840 841 var cannedReadWriteActions = [...]string{ 842 "name/cos:GetBucket", 843 "name/cos:GetBucketObjectVersions", 844 "name/cos:HeadBucket", 845 "name/cos:ListMultipartUploads", 846 "name/cos:ListParts", 847 "name/cos:GetObject", 848 "name/cos:HeadObject", 849 "name/cos:OptionsObject", 850 851 "name/cos:PutObject", 852 "name/cos:PostObject", 853 "name/cos:DeleteObject", 854 "name/cos:InitiateMultipartUpload", 855 "name/cos:UploadPart", 856 "name/cos:CompleteMultipartUpload", 857 "name/cos:AbortMultipartUpload", 858 } 859 860 func getCannedAction(action []string) string { 861 cannedAction := "" 862 863 actionSet := set.New(set.NonThreadSafe) 864 for i := range action { 865 actionSet.Add(action[i]) 866 } 867 if actionSet.Has("name/cos:*") { 868 return "FullControl" 869 } 870 871 readSet := set.New(set.NonThreadSafe) 872 for i := range cannedReadActions { 873 readSet.Add(cannedReadActions[i]) 874 } 875 if set.Difference(readSet, actionSet).Size() == 0 { 876 cannedAction = "Read" 877 } 878 879 readWriteSet := set.New(set.NonThreadSafe) 880 for i := range cannedReadWriteActions { 881 readWriteSet.Add(cannedReadWriteActions[i]) 882 } 883 if set.Difference(readWriteSet, actionSet).Size() == 0 { 884 cannedAction = "ReadWrite" 885 } 886 return cannedAction 887 } 888 889 func (b *SBucket) GetPolicy() ([]cloudprovider.SBucketPolicyStatement, error) { 890 policyOptions := []cloudprovider.SBucketPolicyStatement{} 891 coscli, err := b.region.GetCosClient(b) 892 if err != nil { 893 return nil, errors.Wrap(err, "GetCosClient") 894 } 895 result, _, err := coscli.Bucket.GetPolicy(context.Background()) 896 if err != nil { 897 if strings.Contains(err.Error(), "404") { 898 return nil, nil 899 } 900 return nil, errors.Wrap(err, "GetPolicy") 901 } 902 903 users, err := b.region.client.GetICloudusers() 904 if err != nil { 905 return nil, errors.Wrapf(err, "GetICloudusers") 906 } 907 908 userMaps := map[string]string{} 909 for i := range users { 910 userMaps[fmt.Sprintf("%s:%s", b.region.client.ownerName, users[i].GetGlobalId())] = users[i].GetName() 911 } 912 913 for i := range result.Statement { 914 policyOption := cloudprovider.SBucketPolicyStatement{ 915 Principal: result.Statement[i].Principal, 916 Action: result.Statement[i].Action, 917 Effect: result.Statement[i].Effect, 918 Resource: result.Statement[i].Resource, 919 Condition: result.Statement[i].Condition, 920 921 PrincipalId: getQcsUserId(result.Statement[i].Principal["qcs"]), 922 CannedAction: getCannedAction(result.Statement[i].Action), 923 ResourcePath: getQcsResourcePath(result.Statement[i].Resource), 924 Id: strconv.Itoa(i), 925 } 926 policyOption.PrincipalNames = func() map[string]string { 927 ret := map[string]string{} 928 for _, id := range policyOption.PrincipalId { 929 ret[id], _ = userMaps[id] 930 } 931 return ret 932 }() 933 policyOptions = append(policyOptions, policyOption) 934 } 935 return policyOptions, nil 936 } 937 938 func (b *SBucket) SetPolicy(policy cloudprovider.SBucketPolicyStatementInput) error { 939 coscli, err := b.region.GetCosClient(b) 940 if err != nil { 941 return errors.Wrapf(err, "GetCosClient") 942 } 943 opts := cos.BucketPutPolicyOptions{} 944 opts.Version = "2.0" 945 oldOpts, _, err := coscli.Bucket.GetPolicy(context.Background()) 946 if err != nil { 947 if !strings.Contains(err.Error(), "404") { 948 return errors.Wrap(err, "GetPolicy") 949 } 950 } 951 if len(oldOpts.Statement) > 0 { 952 opts.Statement = oldOpts.Statement 953 } 954 newStatement := cos.BucketStatement{} 955 ids := []string{} 956 for i := range policy.PrincipalId { 957 id := strings.Split(policy.PrincipalId[i], ":") 958 if len(id) == 1 { 959 ids = append(ids, fmt.Sprintf("qcs::cam::uin/%s:uin/%s", id[0], id[0])) 960 } 961 if len(id) == 2 { 962 // 没有主账号id,设为owner id 963 if len(id[0]) == 0 { 964 s, _, err := coscli.Service.Get(context.Background()) 965 if err != nil { 966 return errors.Wrap(err, "coscli.Service.Get") 967 } 968 id[0] = s.Owner.DisplayName 969 } 970 // 没有子账号,默认和主账号相同 971 if len(id[1]) == 0 { 972 id[1] = id[0] 973 } 974 ids = append(ids, fmt.Sprintf("qcs::cam::uin/%s:uin/%s", id[0], id[1])) 975 } 976 if len(id) > 2 { 977 return errors.Wrap(cloudprovider.ErrNotSupported, "Invalida PrincipalId Input") 978 } 979 } 980 principal := map[string][]string{} 981 principal["qcs"] = ids 982 newStatement.Principal = principal 983 newStatement.Effect = policy.Effect 984 resources := []string{} 985 for i := range policy.ResourcePath { 986 resources = append(resources, fmt.Sprintf("qcs::cos:%s:uid/%s:%s%s", b.GetIRegion().GetId(), b.appId, b.getFullName(), policy.ResourcePath[i])) 987 } 988 newStatement.Resource = resources 989 ipEqual := []string{} 990 ipNotEqual := []string{} 991 for i := range policy.IpEquals { 992 ipEqual = append(ipEqual, policy.IpEquals[i]) 993 } 994 for i := range policy.IpNotEquals { 995 ipNotEqual = append(ipNotEqual, policy.IpNotEquals[i]) 996 } 997 condition := map[string]map[string]interface{}{} 998 newStatement.Condition = condition 999 if len(ipEqual) > 0 { 1000 newStatement.Condition["ip_equal"] = map[string]interface{}{"qcs:ip": ipEqual} 1001 } 1002 if len(ipNotEqual) > 0 { 1003 newStatement.Condition["ip_not_equal"] = map[string]interface{}{"qcs:ip": ipNotEqual} 1004 } 1005 1006 if policy.CannedAction == "FullControl" { 1007 newStatement.Action = []string{"name/cos:*"} 1008 } 1009 if policy.CannedAction == "Read" { 1010 newStatement.Action = cannedReadActions[:] 1011 } 1012 if policy.CannedAction == "ReadWrite" { 1013 newStatement.Action = cannedReadWriteActions[:] 1014 } 1015 opts.Statement = append([]cos.BucketStatement{newStatement}, opts.Statement...) 1016 1017 _, err = coscli.Bucket.PutPolicy(context.Background(), &opts) 1018 if err != nil { 1019 log.Errorf("coscli.Bucket.GetACL fail %s", err) 1020 return errors.Wrapf(err, " coscli.Bucket.PutPolicy(context.Background(), %s)", jsonutils.Marshal(opts).String()) 1021 } 1022 return nil 1023 } 1024 1025 func (b *SBucket) DeletePolicy(id []string) ([]cloudprovider.SBucketPolicyStatement, error) { 1026 deletedPolicy := []cloudprovider.SBucketPolicyStatement{} 1027 coscli, err := b.region.GetCosClient(b) 1028 if err != nil { 1029 log.Errorf("GetCosClient fail %s", err) 1030 return nil, errors.Wrap(err, "b.region.GetCosClient(b)") 1031 } 1032 result, _, err := coscli.Bucket.GetPolicy(context.Background()) 1033 if err != nil { 1034 if strings.Contains(err.Error(), "404") { 1035 return nil, nil 1036 } 1037 log.Errorf("coscli.Bucket.GetACL fail %s", err) 1038 return nil, errors.Wrap(err, "coscli.Bucket.GetPolicy(context.Background())") 1039 } 1040 newOpts := cos.BucketPutPolicyOptions{} 1041 newOpts.Version = result.Version 1042 newOpts.Principal = result.Principal 1043 excludeMap := map[int]bool{} 1044 for i := range id { 1045 index, err := strconv.Atoi(id[i]) 1046 if err == nil { 1047 excludeMap[index] = true 1048 } 1049 } 1050 for i := range result.Statement { 1051 if _, ok := excludeMap[i]; !ok { 1052 newOpts.Statement = append(newOpts.Statement, result.Statement[i]) 1053 } else { 1054 deletedPolicy = append(deletedPolicy, cloudprovider.SBucketPolicyStatement{ 1055 Principal: result.Statement[i].Principal, 1056 Action: result.Statement[i].Action, 1057 Effect: result.Statement[i].Effect, 1058 Resource: result.Statement[i].Resource, 1059 Condition: result.Statement[i].Condition, 1060 1061 PrincipalId: getQcsUserId(result.Statement[i].Principal["qcs"]), 1062 CannedAction: getCannedAction(result.Statement[i].Action), 1063 ResourcePath: getQcsResourcePath(result.Statement[i].Resource), 1064 }) 1065 } 1066 } 1067 1068 if len(newOpts.Statement) == 0 { 1069 _, err := coscli.Bucket.DeletePolicy(context.Background()) 1070 if err != nil { 1071 log.Errorf("coscli.Bucket.DeletePolicy fail %s", err) 1072 return nil, errors.Wrap(err, "coscli.Bucket.DeletePolicy(context.Background())") 1073 } 1074 return deletedPolicy, nil 1075 } 1076 1077 _, err = coscli.Bucket.PutPolicy(context.Background(), &newOpts) 1078 if err != nil { 1079 log.Errorf("coscli.Bucket.GetACL fail %s", err) 1080 return nil, errors.Wrapf(err, "coscli.Bucket.PutPolicy(context.Background(), %s)", jsonutils.Marshal(newOpts).String()) 1081 } 1082 return deletedPolicy, nil 1083 } 1084 1085 func (b *SBucket) GetTags() (map[string]string, error) { 1086 coscli, err := b.region.GetCosClient(b) 1087 if err != nil { 1088 return nil, errors.Wrap(err, "GetCosClient") 1089 } 1090 1091 tagresult, _, err := coscli.Bucket.GetTagging(context.Background()) 1092 if err != nil { 1093 if strings.Contains(err.Error(), "404") { 1094 return nil, nil 1095 } 1096 return nil, errors.Wrap(err, "GetTagging") 1097 } 1098 result := map[string]string{} 1099 for i := range tagresult.TagSet { 1100 result[tagresult.TagSet[i].Key] = tagresult.TagSet[i].Value 1101 } 1102 return result, nil 1103 } 1104 1105 func (b *SBucket) SetTags(tags map[string]string, replace bool) error { 1106 if !replace { 1107 return cloudprovider.ErrNotSupported 1108 } 1109 coscli, err := b.region.GetCosClient(b) 1110 if err != nil { 1111 return errors.Wrapf(err, "b.region.GetCosClient(%s)", b.Name) 1112 } 1113 1114 _, err = coscli.Bucket.DeleteTagging(context.Background()) 1115 if err != nil { 1116 return errors.Wrapf(err, "DeleteTagging") 1117 } 1118 1119 if len(tags) == 0 { 1120 return nil 1121 } 1122 1123 input := cos.BucketPutTaggingOptions{} 1124 for k, v := range tags { 1125 input.TagSet = append(input.TagSet, cos.BucketTaggingTag{Key: k, Value: v}) 1126 } 1127 1128 _, err = coscli.Bucket.PutTagging(context.Background(), &input) 1129 if err != nil { 1130 return errors.Wrapf(err, "coscli.Bucket.PutTagging(%s)", jsonutils.Marshal(input)) 1131 } 1132 return nil 1133 } 1134 1135 func (b *SBucket) ListMultipartUploads() ([]cloudprovider.SBucketMultipartUploads, error) { 1136 coscli, err := b.region.GetCosClient(b) 1137 if err != nil { 1138 log.Errorf("GetCosClient fail %s", err) 1139 return nil, errors.Wrap(err, "b.region.GetCosClient(b)") 1140 } 1141 result := []cloudprovider.SBucketMultipartUploads{} 1142 input := cos.ListMultipartUploadsOptions{} 1143 keyMarker := "" 1144 uploadIDMarker := "" 1145 for { 1146 input.KeyMarker = keyMarker 1147 input.UploadIDMarker = uploadIDMarker 1148 output, _, err := coscli.Bucket.ListMultipartUploads(context.Background(), &input) 1149 if err != nil { 1150 return nil, errors.Wrap(err, " coscli.Bucket.ListMultipartUploads(context.Background(), &input)") 1151 } 1152 for i := range output.Uploads { 1153 temp := cloudprovider.SBucketMultipartUploads{ 1154 ObjectName: output.Uploads[i].Key, 1155 UploadID: output.Uploads[i].UploadID, 1156 Initiator: output.Uploads[i].Initiator.DisplayName, 1157 } 1158 temp.Initiated, _ = timeutils.ParseTimeStr(output.Uploads[i].Initiated) 1159 result = append(result, temp) 1160 } 1161 keyMarker = output.NextKeyMarker 1162 uploadIDMarker = output.NextUploadIDMarker 1163 if !output.IsTruncated { 1164 break 1165 } 1166 } 1167 1168 return result, nil 1169 }