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  }