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 }