github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/storage/driver/oss/oss.go (about) 1 // Package oss provides a storagedriver.StorageDriver implementation to 2 // store blobs in Aliyun OSS cloud storage. 3 // 4 // This package leverages the denverdino/aliyungo client library for interfacing with 5 // oss. 6 // 7 // Because OSS is a key, value store the Stat call does not support last modification 8 // time for directories (directories are an abstraction for key, value stores) 9 // 10 // +build include_oss 11 12 package oss 13 14 import ( 15 "bytes" 16 "fmt" 17 "io" 18 "io/ioutil" 19 "net/http" 20 "reflect" 21 "strconv" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/docker/distribution/context" 27 28 "github.com/Sirupsen/logrus" 29 "github.com/denverdino/aliyungo/oss" 30 storagedriver "github.com/docker/distribution/registry/storage/driver" 31 "github.com/docker/distribution/registry/storage/driver/base" 32 "github.com/docker/distribution/registry/storage/driver/factory" 33 ) 34 35 const driverName = "oss" 36 37 // minChunkSize defines the minimum multipart upload chunk size 38 // OSS API requires multipart upload chunks to be at least 5MB 39 const minChunkSize = 5 << 20 40 41 const defaultChunkSize = 2 * minChunkSize 42 43 // listMax is the largest amount of objects you can request from OSS in a list call 44 const listMax = 1000 45 46 //DriverParameters A struct that encapsulates all of the driver parameters after all values have been set 47 type DriverParameters struct { 48 AccessKeyID string 49 AccessKeySecret string 50 Bucket string 51 Region oss.Region 52 Internal bool 53 Encrypt bool 54 Secure bool 55 ChunkSize int64 56 RootDirectory string 57 Endpoint string 58 } 59 60 func init() { 61 factory.Register(driverName, &ossDriverFactory{}) 62 } 63 64 // ossDriverFactory implements the factory.StorageDriverFactory interface 65 type ossDriverFactory struct{} 66 67 func (factory *ossDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) { 68 return FromParameters(parameters) 69 } 70 71 type driver struct { 72 Client *oss.Client 73 Bucket *oss.Bucket 74 ChunkSize int64 75 Encrypt bool 76 RootDirectory string 77 78 pool sync.Pool // pool []byte buffers used for WriteStream 79 zeros []byte // shared, zero-valued buffer used for WriteStream 80 } 81 82 type baseEmbed struct { 83 base.Base 84 } 85 86 // Driver is a storagedriver.StorageDriver implementation backed by Aliyun OSS 87 // Objects are stored at absolute keys in the provided bucket. 88 type Driver struct { 89 baseEmbed 90 } 91 92 // FromParameters constructs a new Driver with a given parameters map 93 // Required parameters: 94 // - accesskey 95 // - secretkey 96 // - region 97 // - bucket 98 // - encrypt 99 func FromParameters(parameters map[string]interface{}) (*Driver, error) { 100 // Providing no values for these is valid in case the user is authenticating 101 // with an IAM on an ec2 instance (in which case the instance credentials will 102 // be summoned when GetAuth is called) 103 accessKey, ok := parameters["accesskeyid"] 104 if !ok { 105 return nil, fmt.Errorf("No accesskeyid parameter provided") 106 } 107 secretKey, ok := parameters["accesskeysecret"] 108 if !ok { 109 return nil, fmt.Errorf("No accesskeysecret parameter provided") 110 } 111 112 regionName, ok := parameters["region"] 113 if !ok || fmt.Sprint(regionName) == "" { 114 return nil, fmt.Errorf("No region parameter provided") 115 } 116 117 bucket, ok := parameters["bucket"] 118 if !ok || fmt.Sprint(bucket) == "" { 119 return nil, fmt.Errorf("No bucket parameter provided") 120 } 121 122 internalBool := false 123 internal, ok := parameters["internal"] 124 if ok { 125 internalBool, ok = internal.(bool) 126 if !ok { 127 return nil, fmt.Errorf("The internal parameter should be a boolean") 128 } 129 } 130 131 encryptBool := false 132 encrypt, ok := parameters["encrypt"] 133 if ok { 134 encryptBool, ok = encrypt.(bool) 135 if !ok { 136 return nil, fmt.Errorf("The encrypt parameter should be a boolean") 137 } 138 } 139 140 secureBool := true 141 secure, ok := parameters["secure"] 142 if ok { 143 secureBool, ok = secure.(bool) 144 if !ok { 145 return nil, fmt.Errorf("The secure parameter should be a boolean") 146 } 147 } 148 149 chunkSize := int64(defaultChunkSize) 150 chunkSizeParam, ok := parameters["chunksize"] 151 if ok { 152 switch v := chunkSizeParam.(type) { 153 case string: 154 vv, err := strconv.ParseInt(v, 0, 64) 155 if err != nil { 156 return nil, fmt.Errorf("chunksize parameter must be an integer, %v invalid", chunkSizeParam) 157 } 158 chunkSize = vv 159 case int64: 160 chunkSize = v 161 case int, uint, int32, uint32, uint64: 162 chunkSize = reflect.ValueOf(v).Convert(reflect.TypeOf(chunkSize)).Int() 163 default: 164 return nil, fmt.Errorf("invalid valud for chunksize: %#v", chunkSizeParam) 165 } 166 167 if chunkSize < minChunkSize { 168 return nil, fmt.Errorf("The chunksize %#v parameter should be a number that is larger than or equal to %d", chunkSize, minChunkSize) 169 } 170 } 171 172 rootDirectory, ok := parameters["rootdirectory"] 173 if !ok { 174 rootDirectory = "" 175 } 176 177 endpoint, ok := parameters["endpoint"] 178 if !ok { 179 endpoint = "" 180 } 181 182 params := DriverParameters{ 183 AccessKeyID: fmt.Sprint(accessKey), 184 AccessKeySecret: fmt.Sprint(secretKey), 185 Bucket: fmt.Sprint(bucket), 186 Region: oss.Region(fmt.Sprint(regionName)), 187 ChunkSize: chunkSize, 188 RootDirectory: fmt.Sprint(rootDirectory), 189 Encrypt: encryptBool, 190 Secure: secureBool, 191 Internal: internalBool, 192 Endpoint: fmt.Sprint(endpoint), 193 } 194 195 return New(params) 196 } 197 198 // New constructs a new Driver with the given AWS credentials, region, encryption flag, and 199 // bucketName 200 func New(params DriverParameters) (*Driver, error) { 201 202 client := oss.NewOSSClient(params.Region, params.Internal, params.AccessKeyID, params.AccessKeySecret, params.Secure) 203 client.SetEndpoint(params.Endpoint) 204 bucket := client.Bucket(params.Bucket) 205 206 // Validate that the given credentials have at least read permissions in the 207 // given bucket scope. 208 if _, err := bucket.List(strings.TrimRight(params.RootDirectory, "/"), "", "", 1); err != nil { 209 return nil, err 210 } 211 212 // TODO(tg123): Currently multipart uploads have no timestamps, so this would be unwise 213 // if you initiated a new OSS client while another one is running on the same bucket. 214 215 d := &driver{ 216 Client: client, 217 Bucket: bucket, 218 ChunkSize: params.ChunkSize, 219 Encrypt: params.Encrypt, 220 RootDirectory: params.RootDirectory, 221 zeros: make([]byte, params.ChunkSize), 222 } 223 224 d.pool.New = func() interface{} { 225 return make([]byte, d.ChunkSize) 226 } 227 228 return &Driver{ 229 baseEmbed: baseEmbed{ 230 Base: base.Base{ 231 StorageDriver: d, 232 }, 233 }, 234 }, nil 235 } 236 237 // Implement the storagedriver.StorageDriver interface 238 239 func (d *driver) Name() string { 240 return driverName 241 } 242 243 // GetContent retrieves the content stored at "path" as a []byte. 244 func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { 245 content, err := d.Bucket.Get(d.ossPath(path)) 246 if err != nil { 247 return nil, parseError(path, err) 248 } 249 return content, nil 250 } 251 252 // PutContent stores the []byte content at a location designated by "path". 253 func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { 254 return parseError(path, d.Bucket.Put(d.ossPath(path), contents, d.getContentType(), getPermissions(), d.getOptions())) 255 } 256 257 // ReadStream retrieves an io.ReadCloser for the content stored at "path" with a 258 // given byte offset. 259 func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io.ReadCloser, error) { 260 headers := make(http.Header) 261 headers.Add("Range", "bytes="+strconv.FormatInt(offset, 10)+"-") 262 263 resp, err := d.Bucket.GetResponseWithHeaders(d.ossPath(path), headers) 264 if err != nil { 265 return nil, parseError(path, err) 266 } 267 268 // Due to Aliyun OSS API, status 200 and whole object will be return instead of an 269 // InvalidRange error when range is invalid. 270 // 271 // OSS sever will always return http.StatusPartialContent if range is acceptable. 272 if resp.StatusCode != http.StatusPartialContent { 273 resp.Body.Close() 274 return ioutil.NopCloser(bytes.NewReader(nil)), nil 275 } 276 277 return resp.Body, nil 278 } 279 280 // WriteStream stores the contents of the provided io.Reader at a 281 // location designated by the given path. The driver will know it has 282 // received the full contents when the reader returns io.EOF. The number 283 // of successfully READ bytes will be returned, even if an error is 284 // returned. May be used to resume writing a stream by providing a nonzero 285 // offset. Offsets past the current size will write from the position 286 // beyond the end of the file. 287 func (d *driver) WriteStream(ctx context.Context, path string, offset int64, reader io.Reader) (totalRead int64, err error) { 288 partNumber := 1 289 bytesRead := 0 290 var putErrChan chan error 291 parts := []oss.Part{} 292 var part oss.Part 293 done := make(chan struct{}) // stopgap to free up waiting goroutines 294 295 multi, err := d.Bucket.InitMulti(d.ossPath(path), d.getContentType(), getPermissions(), d.getOptions()) 296 if err != nil { 297 return 0, err 298 } 299 300 buf := d.getbuf() 301 302 // We never want to leave a dangling multipart upload, our only consistent state is 303 // when there is a whole object at path. This is in order to remain consistent with 304 // the stat call. 305 // 306 // Note that if the machine dies before executing the defer, we will be left with a dangling 307 // multipart upload, which will eventually be cleaned up, but we will lose all of the progress 308 // made prior to the machine crashing. 309 defer func() { 310 if putErrChan != nil { 311 if putErr := <-putErrChan; putErr != nil { 312 err = putErr 313 } 314 } 315 316 if len(parts) > 0 { 317 if multi == nil { 318 // Parts should be empty if the multi is not initialized 319 panic("Unreachable") 320 } else { 321 if multi.Complete(parts) != nil { 322 multi.Abort() 323 } 324 } 325 } 326 327 d.putbuf(buf) // needs to be here to pick up new buf value 328 close(done) // free up any waiting goroutines 329 }() 330 331 // Fills from 0 to total from current 332 fromSmallCurrent := func(total int64) error { 333 current, err := d.ReadStream(ctx, path, 0) 334 if err != nil { 335 return err 336 } 337 338 bytesRead = 0 339 for int64(bytesRead) < total { 340 //The loop should very rarely enter a second iteration 341 nn, err := current.Read(buf[bytesRead:total]) 342 bytesRead += nn 343 if err != nil { 344 if err != io.EOF { 345 return err 346 } 347 348 break 349 } 350 351 } 352 return nil 353 } 354 355 // Fills from parameter to chunkSize from reader 356 fromReader := func(from int64) error { 357 bytesRead = 0 358 for from+int64(bytesRead) < d.ChunkSize { 359 nn, err := reader.Read(buf[from+int64(bytesRead):]) 360 totalRead += int64(nn) 361 bytesRead += nn 362 363 if err != nil { 364 if err != io.EOF { 365 return err 366 } 367 368 break 369 } 370 } 371 372 if putErrChan == nil { 373 putErrChan = make(chan error) 374 } else { 375 if putErr := <-putErrChan; putErr != nil { 376 putErrChan = nil 377 return putErr 378 } 379 } 380 381 go func(bytesRead int, from int64, buf []byte) { 382 defer d.putbuf(buf) // this buffer gets dropped after this call 383 384 // DRAGONS(stevvooe): There are few things one might want to know 385 // about this section. First, the putErrChan is expecting an error 386 // and a nil or just a nil to come through the channel. This is 387 // covered by the silly defer below. The other aspect is the OSS 388 // retry backoff to deal with RequestTimeout errors. Even though 389 // the underlying OSS library should handle it, it doesn't seem to 390 // be part of the shouldRetry function (see denverdino/aliyungo/oss). 391 defer func() { 392 select { 393 case putErrChan <- nil: // for some reason, we do this no matter what. 394 case <-done: 395 return // ensure we don't leak the goroutine 396 } 397 }() 398 399 if bytesRead <= 0 { 400 return 401 } 402 403 var err error 404 var part oss.Part 405 406 loop: 407 for retries := 0; retries < 5; retries++ { 408 part, err = multi.PutPart(int(partNumber), bytes.NewReader(buf[0:int64(bytesRead)+from])) 409 if err == nil { 410 break // success! 411 } 412 413 // NOTE(stevvooe): This retry code tries to only retry under 414 // conditions where the OSS package does not. We may add oss 415 // error codes to the below if we see others bubble up in the 416 // application. Right now, the most troubling is 417 // RequestTimeout, which seems to only triggered when a tcp 418 // connection to OSS slows to a crawl. If the RequestTimeout 419 // ends up getting added to the OSS library and we don't see 420 // other errors, this retry loop can be removed. 421 switch err := err.(type) { 422 case *oss.Error: 423 switch err.Code { 424 case "RequestTimeout": 425 // allow retries on only this error. 426 default: 427 break loop 428 } 429 } 430 431 backoff := 100 * time.Millisecond * time.Duration(retries+1) 432 logrus.Errorf("error putting part, retrying after %v: %v", err, backoff.String()) 433 time.Sleep(backoff) 434 } 435 436 if err != nil { 437 logrus.Errorf("error putting part, aborting: %v", err) 438 select { 439 case putErrChan <- err: 440 case <-done: 441 return // don't leak the goroutine 442 } 443 } 444 445 // parts and partNumber are safe, because this function is the 446 // only one modifying them and we force it to be executed 447 // serially. 448 parts = append(parts, part) 449 partNumber++ 450 }(bytesRead, from, buf) 451 452 buf = d.getbuf() // use a new buffer for the next call 453 return nil 454 } 455 456 if offset > 0 { 457 resp, err := d.Bucket.Head(d.ossPath(path), nil) 458 if err != nil { 459 if ossErr, ok := err.(*oss.Error); !ok || ossErr.Code != "NoSuchKey" { 460 return 0, err 461 } 462 } 463 464 currentLength := int64(0) 465 if err == nil { 466 currentLength = resp.ContentLength 467 } 468 469 if currentLength >= offset { 470 if offset < d.ChunkSize { 471 // chunkSize > currentLength >= offset 472 if err = fromSmallCurrent(offset); err != nil { 473 return totalRead, err 474 } 475 476 if err = fromReader(offset); err != nil { 477 return totalRead, err 478 } 479 480 if totalRead+offset < d.ChunkSize { 481 return totalRead, nil 482 } 483 } else { 484 // currentLength >= offset >= chunkSize 485 _, part, err = multi.PutPartCopy(partNumber, 486 oss.CopyOptions{CopySourceOptions: "bytes=0-" + strconv.FormatInt(offset-1, 10)}, 487 d.Bucket.Path(d.ossPath(path))) 488 if err != nil { 489 return 0, err 490 } 491 492 parts = append(parts, part) 493 partNumber++ 494 } 495 } else { 496 // Fills between parameters with 0s but only when to - from <= chunkSize 497 fromZeroFillSmall := func(from, to int64) error { 498 bytesRead = 0 499 for from+int64(bytesRead) < to { 500 nn, err := bytes.NewReader(d.zeros).Read(buf[from+int64(bytesRead) : to]) 501 bytesRead += nn 502 if err != nil { 503 return err 504 } 505 } 506 507 return nil 508 } 509 510 // Fills between parameters with 0s, making new parts 511 fromZeroFillLarge := func(from, to int64) error { 512 bytesRead64 := int64(0) 513 for to-(from+bytesRead64) >= d.ChunkSize { 514 part, err := multi.PutPart(int(partNumber), bytes.NewReader(d.zeros)) 515 if err != nil { 516 return err 517 } 518 bytesRead64 += d.ChunkSize 519 520 parts = append(parts, part) 521 partNumber++ 522 } 523 524 return fromZeroFillSmall(0, (to-from)%d.ChunkSize) 525 } 526 527 // currentLength < offset 528 if currentLength < d.ChunkSize { 529 if offset < d.ChunkSize { 530 // chunkSize > offset > currentLength 531 if err = fromSmallCurrent(currentLength); err != nil { 532 return totalRead, err 533 } 534 535 if err = fromZeroFillSmall(currentLength, offset); err != nil { 536 return totalRead, err 537 } 538 539 if err = fromReader(offset); err != nil { 540 return totalRead, err 541 } 542 543 if totalRead+offset < d.ChunkSize { 544 return totalRead, nil 545 } 546 } else { 547 // offset >= chunkSize > currentLength 548 if err = fromSmallCurrent(currentLength); err != nil { 549 return totalRead, err 550 } 551 552 if err = fromZeroFillSmall(currentLength, d.ChunkSize); err != nil { 553 return totalRead, err 554 } 555 556 part, err = multi.PutPart(int(partNumber), bytes.NewReader(buf)) 557 if err != nil { 558 return totalRead, err 559 } 560 561 parts = append(parts, part) 562 partNumber++ 563 564 //Zero fill from chunkSize up to offset, then some reader 565 if err = fromZeroFillLarge(d.ChunkSize, offset); err != nil { 566 return totalRead, err 567 } 568 569 if err = fromReader(offset % d.ChunkSize); err != nil { 570 return totalRead, err 571 } 572 573 if totalRead+(offset%d.ChunkSize) < d.ChunkSize { 574 return totalRead, nil 575 } 576 } 577 } else { 578 // offset > currentLength >= chunkSize 579 _, part, err = multi.PutPartCopy(partNumber, 580 oss.CopyOptions{}, 581 d.Bucket.Path(d.ossPath(path))) 582 if err != nil { 583 return 0, err 584 } 585 586 parts = append(parts, part) 587 partNumber++ 588 589 //Zero fill from currentLength up to offset, then some reader 590 if err = fromZeroFillLarge(currentLength, offset); err != nil { 591 return totalRead, err 592 } 593 594 if err = fromReader((offset - currentLength) % d.ChunkSize); err != nil { 595 return totalRead, err 596 } 597 598 if totalRead+((offset-currentLength)%d.ChunkSize) < d.ChunkSize { 599 return totalRead, nil 600 } 601 } 602 603 } 604 } 605 606 for { 607 if err = fromReader(0); err != nil { 608 return totalRead, err 609 } 610 611 if int64(bytesRead) < d.ChunkSize { 612 break 613 } 614 } 615 616 return totalRead, nil 617 } 618 619 // Stat retrieves the FileInfo for the given path, including the current size 620 // in bytes and the creation time. 621 func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) { 622 listResponse, err := d.Bucket.List(d.ossPath(path), "", "", 1) 623 if err != nil { 624 return nil, err 625 } 626 627 fi := storagedriver.FileInfoFields{ 628 Path: path, 629 } 630 631 if len(listResponse.Contents) == 1 { 632 if listResponse.Contents[0].Key != d.ossPath(path) { 633 fi.IsDir = true 634 } else { 635 fi.IsDir = false 636 fi.Size = listResponse.Contents[0].Size 637 638 timestamp, err := time.Parse(time.RFC3339Nano, listResponse.Contents[0].LastModified) 639 if err != nil { 640 return nil, err 641 } 642 fi.ModTime = timestamp 643 } 644 } else if len(listResponse.CommonPrefixes) == 1 { 645 fi.IsDir = true 646 } else { 647 return nil, storagedriver.PathNotFoundError{Path: path} 648 } 649 650 return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil 651 } 652 653 // List returns a list of the objects that are direct descendants of the given path. 654 func (d *driver) List(ctx context.Context, opath string) ([]string, error) { 655 path := opath 656 if path != "/" && opath[len(path)-1] != '/' { 657 path = path + "/" 658 } 659 660 // This is to cover for the cases when the rootDirectory of the driver is either "" or "/". 661 // In those cases, there is no root prefix to replace and we must actually add a "/" to all 662 // results in order to keep them as valid paths as recognized by storagedriver.PathRegexp 663 prefix := "" 664 if d.ossPath("") == "" { 665 prefix = "/" 666 } 667 668 listResponse, err := d.Bucket.List(d.ossPath(path), "/", "", listMax) 669 if err != nil { 670 return nil, parseError(opath, err) 671 } 672 673 files := []string{} 674 directories := []string{} 675 676 for { 677 for _, key := range listResponse.Contents { 678 files = append(files, strings.Replace(key.Key, d.ossPath(""), prefix, 1)) 679 } 680 681 for _, commonPrefix := range listResponse.CommonPrefixes { 682 directories = append(directories, strings.Replace(commonPrefix[0:len(commonPrefix)-1], d.ossPath(""), prefix, 1)) 683 } 684 685 if listResponse.IsTruncated { 686 listResponse, err = d.Bucket.List(d.ossPath(path), "/", listResponse.NextMarker, listMax) 687 if err != nil { 688 return nil, err 689 } 690 } else { 691 break 692 } 693 } 694 695 if opath != "/" { 696 if len(files) == 0 && len(directories) == 0 { 697 // Treat empty response as missing directory, since we don't actually 698 // have directories in s3. 699 return nil, storagedriver.PathNotFoundError{Path: opath} 700 } 701 } 702 703 return append(files, directories...), nil 704 } 705 706 // Move moves an object stored at sourcePath to destPath, removing the original 707 // object. 708 func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { 709 logrus.Infof("Move from %s to %s", d.Bucket.Path("/"+d.ossPath(sourcePath)), d.ossPath(destPath)) 710 /* This is terrible, but aws doesn't have an actual move. */ 711 _, err := d.Bucket.PutCopy(d.ossPath(destPath), getPermissions(), 712 oss.CopyOptions{ 713 //Options: d.getOptions(), 714 //ContentType: d.getContentType() 715 }, 716 d.Bucket.Path(d.ossPath(sourcePath))) 717 if err != nil { 718 return parseError(sourcePath, err) 719 } 720 721 return d.Delete(ctx, sourcePath) 722 } 723 724 // Delete recursively deletes all objects stored at "path" and its subpaths. 725 func (d *driver) Delete(ctx context.Context, path string) error { 726 listResponse, err := d.Bucket.List(d.ossPath(path), "", "", listMax) 727 if err != nil || len(listResponse.Contents) == 0 { 728 return storagedriver.PathNotFoundError{Path: path} 729 } 730 731 ossObjects := make([]oss.Object, listMax) 732 733 for len(listResponse.Contents) > 0 { 734 for index, key := range listResponse.Contents { 735 ossObjects[index].Key = key.Key 736 } 737 738 err := d.Bucket.DelMulti(oss.Delete{Quiet: false, Objects: ossObjects[0:len(listResponse.Contents)]}) 739 if err != nil { 740 return nil 741 } 742 743 listResponse, err = d.Bucket.List(d.ossPath(path), "", "", listMax) 744 if err != nil { 745 return err 746 } 747 } 748 749 return nil 750 } 751 752 // URLFor returns a URL which may be used to retrieve the content stored at the given path. 753 // May return an UnsupportedMethodErr in certain StorageDriver implementations. 754 func (d *driver) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) { 755 methodString := "GET" 756 method, ok := options["method"] 757 if ok { 758 methodString, ok = method.(string) 759 if !ok || (methodString != "GET" && methodString != "HEAD") { 760 return "", storagedriver.ErrUnsupportedMethod{} 761 } 762 } 763 764 expiresTime := time.Now().Add(20 * time.Minute) 765 logrus.Infof("expiresTime: %d", expiresTime) 766 767 expires, ok := options["expiry"] 768 if ok { 769 et, ok := expires.(time.Time) 770 if ok { 771 expiresTime = et 772 } 773 } 774 logrus.Infof("expiresTime: %d", expiresTime) 775 testURL := d.Bucket.SignedURLWithMethod(methodString, d.ossPath(path), expiresTime, nil, nil) 776 logrus.Infof("testURL: %s", testURL) 777 return testURL, nil 778 } 779 780 func (d *driver) ossPath(path string) string { 781 return strings.TrimLeft(strings.TrimRight(d.RootDirectory, "/")+path, "/") 782 } 783 784 // S3BucketKey returns the OSS bucket key for the given storage driver path. 785 func (d *Driver) S3BucketKey(path string) string { 786 return d.StorageDriver.(*driver).ossPath(path) 787 } 788 789 func parseError(path string, err error) error { 790 if ossErr, ok := err.(*oss.Error); ok && ossErr.Code == "NoSuchKey" { 791 return storagedriver.PathNotFoundError{Path: path} 792 } 793 794 return err 795 } 796 797 func hasCode(err error, code string) bool { 798 ossErr, ok := err.(*oss.Error) 799 return ok && ossErr.Code == code 800 } 801 802 func (d *driver) getOptions() oss.Options { 803 return oss.Options{ServerSideEncryption: d.Encrypt} 804 } 805 806 func getPermissions() oss.ACL { 807 return oss.Private 808 } 809 810 func (d *driver) getContentType() string { 811 return "application/octet-stream" 812 } 813 814 // getbuf returns a buffer from the driver's pool with length d.ChunkSize. 815 func (d *driver) getbuf() []byte { 816 return d.pool.Get().([]byte) 817 } 818 819 func (d *driver) putbuf(p []byte) { 820 copy(p, d.zeros) 821 d.pool.Put(p) 822 }