yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/ucloud/ufile.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 ucloud
    16  
    17  import (
    18  	"context"
    19  	"crypto/hmac"
    20  	"crypto/sha1"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"net/url"
    26  	"strconv"
    27  	"time"
    28  
    29  	"yunion.io/x/jsonutils"
    30  	"yunion.io/x/log"
    31  	"yunion.io/x/pkg/errors"
    32  
    33  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    34  	"yunion.io/x/cloudmux/pkg/multicloud"
    35  	"yunion.io/x/onecloud/pkg/util/httputils"
    36  )
    37  
    38  type SBucket struct {
    39  	multicloud.SBaseBucket
    40  	UcloudTags
    41  
    42  	region *SRegion
    43  
    44  	// projectId string
    45  
    46  	Domain        Domain   `json:"Domain"`
    47  	BucketID      string   `json:"BucketId"`
    48  	Region        string   `json:"Region"`
    49  	CreateTime    int64    `json:"CreateTime"`
    50  	Biz           string   `json:"Biz"`
    51  	BucketName    string   `json:"BucketName"`
    52  	ModifyTime    int64    `json:"ModifyTime"`
    53  	Type          string   `json:"Type"`
    54  	Tag           string   `json:"Tag"`
    55  	HasUserDomain int64    `json:"HasUserDomain"`
    56  	CDNDomainID   []string `json:"CdnDomainId"`
    57  }
    58  
    59  type Domain struct {
    60  	Src       []string      `json:"Src"`
    61  	CDN       []string      `json:"Cdn"`
    62  	CustomCDN []interface{} `json:"CustomCdn"`
    63  	CustomSrc []interface{} `json:"CustomSrc"`
    64  }
    65  
    66  type SFile struct {
    67  	bucket *SBucket
    68  
    69  	BucketName   string `json:"BucketName"`
    70  	FileName     string `json:"FileName"`
    71  	Size         int64  `json:"Size"`
    72  	Hash         string `json:"Hash"`
    73  	MimeType     string `json:"MimeType"`
    74  	CreateTime   int64  `json:"CreateTime"`
    75  	ModifyTime   int64  `json:"ModifyTime"`
    76  	StorageClass string `json:"StorageClass"`
    77  
    78  	file io.Reader
    79  }
    80  
    81  func (client *SUcloudClient) signHeader(httpMethod string, path string, md5 string) string {
    82  	contentType := ""
    83  	if httpMethod == http.MethodPut {
    84  		contentType = "application/octet-stream"
    85  	}
    86  
    87  	data := httpMethod + "\n"
    88  	data += md5 + "\n"
    89  	data += contentType + "\n"
    90  	data += "\n"
    91  	data += path
    92  
    93  	log.Debugf("sign %s", data)
    94  	h := hmac.New(sha1.New, []byte(client.accessKeySecret))
    95  	h.Write([]byte(data))
    96  	return base64.StdEncoding.EncodeToString(h.Sum(nil))
    97  }
    98  
    99  func (self *SFile) signHeader(httpMethod string) string {
   100  	return self.bucket.region.client.signHeader(httpMethod, "/"+self.bucket.BucketName+"/"+self.FileName, self.Hash)
   101  }
   102  
   103  func (self *SFile) auth(httpMethod string) string {
   104  	return "UCloud" + " " + self.bucket.region.client.accessKeyId + ":" + self.signHeader(httpMethod)
   105  }
   106  
   107  func (self *SFile) GetHost() string {
   108  	return self.bucket.Domain.Src[0]
   109  }
   110  
   111  func (self *SFile) GetUrl() string {
   112  	return fmt.Sprintf("http://%s/%s", self.GetHost(), self.FileName)
   113  }
   114  
   115  // https://github.com/ufilesdk-dev/ufile-gosdk/blob/master/auth.go
   116  func (self *SFile) FetchFileUrl() string {
   117  	expired := strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10)
   118  	// sign
   119  	data := "GET\n\n\n" + expired + "\n"
   120  	data += "/" + self.bucket.BucketName + "/" + self.FileName
   121  	h := hmac.New(sha1.New, []byte(self.bucket.region.client.accessKeySecret))
   122  	h.Write([]byte(data))
   123  	sign := base64.StdEncoding.EncodeToString(h.Sum(nil))
   124  
   125  	urlEncoder := url.Values{}
   126  	urlEncoder.Add("UCloudPublicKey", self.bucket.region.client.accessKeyId)
   127  	urlEncoder.Add("Signature", sign)
   128  	urlEncoder.Add("Expires", expired)
   129  	querys := urlEncoder.Encode()
   130  	return fmt.Sprintf("%s?%s", self.GetUrl(), querys)
   131  }
   132  
   133  func (self *SFile) Upload() error {
   134  	req, _ := http.NewRequest(http.MethodPut, self.GetUrl(), self.file)
   135  	req.Header.Add("Authorization", self.auth(http.MethodPut))
   136  	req.Header.Add("Content-MD5", self.Hash)
   137  	req.Header.Add("Content-Type", "application/octet-stream")
   138  	req.Header.Add("Content-Length", strconv.FormatInt(self.Size, 10))
   139  	_, err := doRequest(req)
   140  	return err
   141  }
   142  
   143  func (self *SFile) Delete() error {
   144  	req, _ := http.NewRequest(http.MethodDelete, self.GetUrl(), nil)
   145  	req.Header.Add("Authorization", self.auth(http.MethodDelete))
   146  	_, err := doRequest(req)
   147  	return err
   148  }
   149  
   150  func (self *SFile) GetIBucket() cloudprovider.ICloudBucket {
   151  	return self.bucket
   152  }
   153  
   154  func (self *SFile) GetKey() string {
   155  	return self.FileName
   156  }
   157  
   158  func (self *SFile) GetSizeBytes() int64 {
   159  	return self.Size
   160  }
   161  
   162  func (self *SFile) GetLastModified() time.Time {
   163  	return time.Unix(self.ModifyTime, 0)
   164  }
   165  
   166  func (self *SFile) GetStorageClass() string {
   167  	return self.StorageClass
   168  }
   169  
   170  func (self *SFile) GetETag() string {
   171  	return self.Hash
   172  }
   173  
   174  func (self *SFile) GetContentType() string {
   175  	return self.MimeType
   176  }
   177  
   178  func (self *SFile) GetAcl() cloudprovider.TBucketACLType {
   179  	return self.bucket.GetAcl()
   180  }
   181  
   182  func (self *SFile) SetAcl(cloudprovider.TBucketACLType) error {
   183  	return nil
   184  }
   185  
   186  func (self *SFile) GetMeta() http.Header {
   187  	return nil
   188  }
   189  
   190  func (self *SFile) SetMeta(ctx context.Context, meta http.Header) error {
   191  	return cloudprovider.ErrNotSupported
   192  }
   193  
   194  func doRequest(req *http.Request) (jsonutils.JSONObject, error) {
   195  	// ufile request use no timeout client so as to download/upload large files
   196  	res, err := httputils.GetAdaptiveTimeoutClient().Do(req)
   197  	if err != nil {
   198  		return nil, errors.Wrap(err, "httpclient Do")
   199  	}
   200  	_, body, err := httputils.ParseJSONResponse("", res, err, false)
   201  	if err != nil {
   202  		return nil, errors.Wrap(err, "ParseJSONResponse")
   203  	}
   204  	return body, nil
   205  }
   206  
   207  type sPrefixFileListOutput struct {
   208  	BucketName string
   209  	BucketId   string
   210  	NextMarker string
   211  	DataSet    []SFile
   212  }
   213  
   214  func (b *SBucket) doPrefixFileList(prefix string, marker string, limit int) (*sPrefixFileListOutput, error) {
   215  	params := jsonutils.NewDict()
   216  	params.Add(jsonutils.NewString(""), "list")
   217  	if len(prefix) > 0 {
   218  		params.Add(jsonutils.NewString(prefix), "prefix")
   219  	}
   220  	if len(marker) > 0 {
   221  		params.Add(jsonutils.NewString(marker), "marker")
   222  	}
   223  	if limit > 0 {
   224  		params.Add(jsonutils.NewInt(int64(limit)), "limit")
   225  	}
   226  	host := fmt.Sprintf("https://%s.ufile.ucloud.cn", b.BucketName)
   227  	path := fmt.Sprintf("/?%s", params.QueryString())
   228  
   229  	log.Debugf("Request %s%s", host, path)
   230  
   231  	req, _ := http.NewRequest(http.MethodGet, host+path, nil)
   232  
   233  	sign := b.region.client.signHeader(http.MethodGet, path, "")
   234  	auth := "UCloud" + " " + b.region.client.accessKeyId + ":" + sign
   235  
   236  	req.Header.Add("Authorization", auth)
   237  
   238  	output := sPrefixFileListOutput{}
   239  
   240  	body, err := doRequest(req)
   241  	if err != nil {
   242  		return nil, errors.Wrap(err, "doRequest")
   243  	}
   244  	err = body.Unmarshal(&output)
   245  	if err != nil {
   246  		return nil, errors.Wrap(err, "body.Unmarshal")
   247  	}
   248  
   249  	return &output, nil
   250  }
   251  
   252  func (b *SBucket) GetProjectId() string {
   253  	return b.region.client.projectId
   254  }
   255  
   256  func (b *SBucket) GetGlobalId() string {
   257  	return b.BucketID
   258  }
   259  
   260  func (b *SBucket) GetName() string {
   261  	return b.BucketName
   262  }
   263  
   264  func (b *SBucket) GetLocation() string {
   265  	return b.region.GetId()
   266  }
   267  
   268  func (b *SBucket) GetIRegion() cloudprovider.ICloudRegion {
   269  	return b.region
   270  }
   271  
   272  func (b *SBucket) GetCreatedAt() time.Time {
   273  	return time.Unix(b.CreateTime, 0)
   274  }
   275  
   276  func (b *SBucket) GetStorageClass() string {
   277  	return ""
   278  }
   279  
   280  func (b *SBucket) GetAcl() cloudprovider.TBucketACLType {
   281  	switch b.Type {
   282  	case "public":
   283  		return cloudprovider.ACLPublicRead
   284  	default:
   285  		return cloudprovider.ACLPrivate
   286  	}
   287  }
   288  
   289  func (b *SBucket) SetAcl(aclStr cloudprovider.TBucketACLType) error {
   290  	aclType := "private"
   291  	if aclStr == cloudprovider.ACLPublicRead || aclStr == cloudprovider.ACLPublicReadWrite {
   292  		aclType = "public"
   293  	}
   294  	return b.region.updateBucket(b.BucketName, aclType)
   295  }
   296  
   297  func (b *SBucket) getSrcUrl() string {
   298  	if len(b.Domain.Src) > 0 {
   299  		return b.Domain.Src[0]
   300  	}
   301  	return ""
   302  }
   303  
   304  func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
   305  	ret := make([]cloudprovider.SBucketAccessUrl, 0)
   306  	for i, u := range b.Domain.Src {
   307  		primary := false
   308  		if i == 0 {
   309  			primary = true
   310  		}
   311  		ret = append(ret, cloudprovider.SBucketAccessUrl{
   312  			Url:         u,
   313  			Description: fmt.Sprintf("src%d", i),
   314  			Primary:     primary,
   315  		})
   316  	}
   317  	for i, u := range b.Domain.CDN {
   318  		ret = append(ret, cloudprovider.SBucketAccessUrl{
   319  			Url:         u,
   320  			Description: fmt.Sprintf("cdn%d", i),
   321  		})
   322  	}
   323  	return ret
   324  }
   325  
   326  func (b *SBucket) GetStats() cloudprovider.SBucketStats {
   327  	stats, _ := cloudprovider.GetIBucketStats(b)
   328  	return stats
   329  }
   330  
   331  func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
   332  	result := cloudprovider.SListObjectResult{}
   333  
   334  	output, err := b.doPrefixFileList(prefix, marker, maxCount)
   335  	if err != nil {
   336  		return result, errors.Wrap(err, "b.doPrefixFileList")
   337  	}
   338  
   339  	if len(output.NextMarker) > 0 {
   340  		result.NextMarker = output.NextMarker
   341  		result.IsTruncated = true
   342  	}
   343  
   344  	result.Objects = make([]cloudprovider.ICloudObject, len(output.DataSet))
   345  	for i := range output.DataSet {
   346  		result.Objects[i] = &output.DataSet[i]
   347  	}
   348  
   349  	return result, nil
   350  }
   351  
   352  func (b *SBucket) PutObject(ctx context.Context, key string, input io.Reader, sizeBytes int64, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error {
   353  	return cloudprovider.ErrNotSupported
   354  }
   355  
   356  func (b *SBucket) NewMultipartUpload(ctx context.Context, key string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) (string, error) {
   357  	return "", cloudprovider.ErrNotSupported
   358  }
   359  
   360  func (b *SBucket) UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64, offset, totalSize int64) (string, error) {
   361  	return "", cloudprovider.ErrNotSupported
   362  }
   363  
   364  func (b *SBucket) CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error {
   365  	return cloudprovider.ErrNotSupported
   366  }
   367  
   368  func (b *SBucket) AbortMultipartUpload(ctx context.Context, key string, uploadId string) error {
   369  	return cloudprovider.ErrNotSupported
   370  }
   371  
   372  func (b *SBucket) DeleteObject(ctx context.Context, key string) error {
   373  	file := SFile{
   374  		bucket:   b,
   375  		FileName: key,
   376  	}
   377  	return file.Delete()
   378  }
   379  
   380  func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
   381  	return "", cloudprovider.ErrNotSupported
   382  }
   383  
   384  func (b *SBucket) CopyObject(ctx context.Context, destKey string, srcBucket, srcKey string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error {
   385  	return cloudprovider.ErrNotSupported
   386  }
   387  
   388  func (b *SBucket) GetObject(ctx context.Context, key string, rangeOpt *cloudprovider.SGetObjectRange) (io.ReadCloser, error) {
   389  	return nil, cloudprovider.ErrNotSupported
   390  }
   391  
   392  func (b *SBucket) CopyPart(ctx context.Context, key string, uploadId string, partIndex int, srcBucketName string, srcKey string, srcOffset int64, srcLength int64) (string, error) {
   393  	return "", cloudprovider.ErrNotSupported
   394  }