github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/storage/driver/swift/swift.go (about) 1 // Package swift provides a storagedriver.StorageDriver implementation to 2 // store blobs in Openstack Swift object storage. 3 // 4 // This package leverages the ncw/swift client library for interfacing with 5 // Swift. 6 // 7 // It supports both TempAuth authentication and Keystone authentication 8 // (up to version 3). 9 // 10 // As Swift has a limit on the size of a single uploaded object (by default 11 // this is 5GB), the driver makes use of the Swift Large Object Support 12 // (http://docs.openstack.org/developer/swift/overview_large_objects.html). 13 // Only one container is used for both manifests and data objects. Manifests 14 // are stored in the 'files' pseudo directory, data objects are stored under 15 // 'segments'. 16 package swift 17 18 import ( 19 "bytes" 20 "crypto/md5" 21 "crypto/rand" 22 "crypto/sha1" 23 "crypto/tls" 24 "encoding/hex" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "net/http" 29 "net/url" 30 "strconv" 31 "strings" 32 "time" 33 34 "github.com/mitchellh/mapstructure" 35 "github.com/ncw/swift" 36 37 "github.com/docker/distribution/context" 38 storagedriver "github.com/docker/distribution/registry/storage/driver" 39 "github.com/docker/distribution/registry/storage/driver/base" 40 "github.com/docker/distribution/registry/storage/driver/factory" 41 "github.com/docker/distribution/version" 42 ) 43 44 const driverName = "swift" 45 46 // defaultChunkSize defines the default size of a segment 47 const defaultChunkSize = 20 * 1024 * 1024 48 49 // minChunkSize defines the minimum size of a segment 50 const minChunkSize = 1 << 20 51 52 // readAfterWriteTimeout defines the time we wait before an object appears after having been uploaded 53 var readAfterWriteTimeout = 15 * time.Second 54 55 // readAfterWriteWait defines the time to sleep between two retries 56 var readAfterWriteWait = 200 * time.Millisecond 57 58 // Parameters A struct that encapsulates all of the driver parameters after all values have been set 59 type Parameters struct { 60 Username string 61 Password string 62 AuthURL string 63 Tenant string 64 TenantID string 65 Domain string 66 DomainID string 67 TrustID string 68 Region string 69 Container string 70 Prefix string 71 InsecureSkipVerify bool 72 ChunkSize int 73 SecretKey string 74 AccessKey string 75 TempURLContainerKey bool 76 TempURLMethods []string 77 } 78 79 // swiftInfo maps the JSON structure returned by Swift /info endpoint 80 type swiftInfo struct { 81 Swift struct { 82 Version string `mapstructure:"version"` 83 } 84 Tempurl struct { 85 Methods []string `mapstructure:"methods"` 86 } 87 } 88 89 func init() { 90 factory.Register(driverName, &swiftDriverFactory{}) 91 } 92 93 // swiftDriverFactory implements the factory.StorageDriverFactory interface 94 type swiftDriverFactory struct{} 95 96 func (factory *swiftDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) { 97 return FromParameters(parameters) 98 } 99 100 type driver struct { 101 Conn swift.Connection 102 Container string 103 Prefix string 104 BulkDeleteSupport bool 105 ChunkSize int 106 SecretKey string 107 AccessKey string 108 TempURLContainerKey bool 109 TempURLMethods []string 110 } 111 112 type baseEmbed struct { 113 base.Base 114 } 115 116 // Driver is a storagedriver.StorageDriver implementation backed by Openstack Swift 117 // Objects are stored at absolute keys in the provided container. 118 type Driver struct { 119 baseEmbed 120 } 121 122 // FromParameters constructs a new Driver with a given parameters map 123 // Required parameters: 124 // - username 125 // - password 126 // - authurl 127 // - container 128 func FromParameters(parameters map[string]interface{}) (*Driver, error) { 129 params := Parameters{ 130 ChunkSize: defaultChunkSize, 131 InsecureSkipVerify: false, 132 } 133 134 if err := mapstructure.Decode(parameters, ¶ms); err != nil { 135 return nil, err 136 } 137 138 if params.Username == "" { 139 return nil, fmt.Errorf("No username parameter provided") 140 } 141 142 if params.Password == "" { 143 return nil, fmt.Errorf("No password parameter provided") 144 } 145 146 if params.AuthURL == "" { 147 return nil, fmt.Errorf("No authurl parameter provided") 148 } 149 150 if params.Container == "" { 151 return nil, fmt.Errorf("No container parameter provided") 152 } 153 154 if params.ChunkSize < minChunkSize { 155 return nil, fmt.Errorf("The chunksize %#v parameter should be a number that is larger than or equal to %d", params.ChunkSize, minChunkSize) 156 } 157 158 return New(params) 159 } 160 161 // New constructs a new Driver with the given Openstack Swift credentials and container name 162 func New(params Parameters) (*Driver, error) { 163 transport := &http.Transport{ 164 Proxy: http.ProxyFromEnvironment, 165 MaxIdleConnsPerHost: 2048, 166 TLSClientConfig: &tls.Config{InsecureSkipVerify: params.InsecureSkipVerify}, 167 } 168 169 ct := swift.Connection{ 170 UserName: params.Username, 171 ApiKey: params.Password, 172 AuthUrl: params.AuthURL, 173 Region: params.Region, 174 UserAgent: "distribution/" + version.Version, 175 Tenant: params.Tenant, 176 TenantId: params.TenantID, 177 Domain: params.Domain, 178 DomainId: params.DomainID, 179 TrustId: params.TrustID, 180 Transport: transport, 181 ConnectTimeout: 60 * time.Second, 182 Timeout: 15 * 60 * time.Second, 183 } 184 err := ct.Authenticate() 185 if err != nil { 186 return nil, fmt.Errorf("Swift authentication failed: %s", err) 187 } 188 189 if _, _, err := ct.Container(params.Container); err == swift.ContainerNotFound { 190 if err := ct.ContainerCreate(params.Container, nil); err != nil { 191 return nil, fmt.Errorf("Failed to create container %s (%s)", params.Container, err) 192 } 193 } else if err != nil { 194 return nil, fmt.Errorf("Failed to retrieve info about container %s (%s)", params.Container, err) 195 } 196 197 d := &driver{ 198 Conn: ct, 199 Container: params.Container, 200 Prefix: params.Prefix, 201 ChunkSize: params.ChunkSize, 202 TempURLMethods: make([]string, 0), 203 AccessKey: params.AccessKey, 204 } 205 206 info := swiftInfo{} 207 if config, err := d.Conn.QueryInfo(); err == nil { 208 _, d.BulkDeleteSupport = config["bulk_delete"] 209 210 if err := mapstructure.Decode(config, &info); err == nil { 211 d.TempURLContainerKey = info.Swift.Version >= "2.3.0" 212 d.TempURLMethods = info.Tempurl.Methods 213 } 214 } else { 215 d.TempURLContainerKey = params.TempURLContainerKey 216 d.TempURLMethods = params.TempURLMethods 217 } 218 219 if len(d.TempURLMethods) > 0 { 220 secretKey := params.SecretKey 221 if secretKey == "" { 222 secretKey, _ = generateSecret() 223 } 224 225 // Since Swift 2.2.2, we can now set secret keys on containers 226 // in addition to the account secret keys. Use them in preference. 227 if d.TempURLContainerKey { 228 _, containerHeaders, err := d.Conn.Container(d.Container) 229 if err != nil { 230 return nil, fmt.Errorf("Failed to fetch container info %s (%s)", d.Container, err) 231 } 232 233 d.SecretKey = containerHeaders["X-Container-Meta-Temp-Url-Key"] 234 if d.SecretKey == "" || (params.SecretKey != "" && d.SecretKey != params.SecretKey) { 235 m := swift.Metadata{} 236 m["temp-url-key"] = secretKey 237 if d.Conn.ContainerUpdate(d.Container, m.ContainerHeaders()); err == nil { 238 d.SecretKey = secretKey 239 } 240 } 241 } else { 242 // Use the account secret key 243 _, accountHeaders, err := d.Conn.Account() 244 if err != nil { 245 return nil, fmt.Errorf("Failed to fetch account info (%s)", err) 246 } 247 248 d.SecretKey = accountHeaders["X-Account-Meta-Temp-Url-Key"] 249 if d.SecretKey == "" || (params.SecretKey != "" && d.SecretKey != params.SecretKey) { 250 m := swift.Metadata{} 251 m["temp-url-key"] = secretKey 252 if err := d.Conn.AccountUpdate(m.AccountHeaders()); err == nil { 253 d.SecretKey = secretKey 254 } 255 } 256 } 257 } 258 259 return &Driver{ 260 baseEmbed: baseEmbed{ 261 Base: base.Base{ 262 StorageDriver: d, 263 }, 264 }, 265 }, nil 266 } 267 268 // Implement the storagedriver.StorageDriver interface 269 270 func (d *driver) Name() string { 271 return driverName 272 } 273 274 // GetContent retrieves the content stored at "path" as a []byte. 275 func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) { 276 content, err := d.Conn.ObjectGetBytes(d.Container, d.swiftPath(path)) 277 if err == swift.ObjectNotFound { 278 return nil, storagedriver.PathNotFoundError{Path: path} 279 } 280 return content, nil 281 } 282 283 // PutContent stores the []byte content at a location designated by "path". 284 func (d *driver) PutContent(ctx context.Context, path string, contents []byte) error { 285 err := d.Conn.ObjectPutBytes(d.Container, d.swiftPath(path), contents, d.getContentType()) 286 if err == swift.ObjectNotFound { 287 return storagedriver.PathNotFoundError{Path: path} 288 } 289 return err 290 } 291 292 // ReadStream retrieves an io.ReadCloser for the content stored at "path" with a 293 // given byte offset. 294 func (d *driver) ReadStream(ctx context.Context, path string, offset int64) (io.ReadCloser, error) { 295 headers := make(swift.Headers) 296 headers["Range"] = "bytes=" + strconv.FormatInt(offset, 10) + "-" 297 298 file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) 299 if err == swift.ObjectNotFound { 300 return nil, storagedriver.PathNotFoundError{Path: path} 301 } 302 if swiftErr, ok := err.(*swift.Error); ok && swiftErr.StatusCode == http.StatusRequestedRangeNotSatisfiable { 303 return ioutil.NopCloser(bytes.NewReader(nil)), nil 304 } 305 return file, err 306 } 307 308 // WriteStream stores the contents of the provided io.Reader at a 309 // location designated by the given path. The driver will know it has 310 // received the full contents when the reader returns io.EOF. The number 311 // of successfully READ bytes will be returned, even if an error is 312 // returned. May be used to resume writing a stream by providing a nonzero 313 // offset. Offsets past the current size will write from the position 314 // beyond the end of the file. 315 func (d *driver) WriteStream(ctx context.Context, path string, offset int64, reader io.Reader) (int64, error) { 316 var ( 317 segments []swift.Object 318 multi io.Reader 319 paddingReader io.Reader 320 currentLength int64 321 cursor int64 322 segmentPath string 323 ) 324 325 partNumber := 1 326 chunkSize := int64(d.ChunkSize) 327 zeroBuf := make([]byte, d.ChunkSize) 328 hash := md5.New() 329 330 getSegment := func() string { 331 return fmt.Sprintf("%s/%016d", segmentPath, partNumber) 332 } 333 334 max := func(a int64, b int64) int64 { 335 if a > b { 336 return a 337 } 338 return b 339 } 340 341 createManifest := true 342 info, headers, err := d.Conn.Object(d.Container, d.swiftPath(path)) 343 if err == nil { 344 manifest, ok := headers["X-Object-Manifest"] 345 if !ok { 346 if segmentPath, err = d.swiftSegmentPath(path); err != nil { 347 return 0, err 348 } 349 if err := d.Conn.ObjectMove(d.Container, d.swiftPath(path), d.Container, getSegment()); err != nil { 350 return 0, err 351 } 352 segments = append(segments, info) 353 } else { 354 _, segmentPath = parseManifest(manifest) 355 if segments, err = d.getAllSegments(segmentPath); err != nil { 356 return 0, err 357 } 358 createManifest = false 359 } 360 currentLength = info.Bytes 361 } else if err == swift.ObjectNotFound { 362 if segmentPath, err = d.swiftSegmentPath(path); err != nil { 363 return 0, err 364 } 365 } else { 366 return 0, err 367 } 368 369 // First, we skip the existing segments that are not modified by this call 370 for i := range segments { 371 if offset < cursor+segments[i].Bytes { 372 break 373 } 374 cursor += segments[i].Bytes 375 hash.Write([]byte(segments[i].Hash)) 376 partNumber++ 377 } 378 379 // We reached the end of the file but we haven't reached 'offset' yet 380 // Therefore we add blocks of zeros 381 if offset >= currentLength { 382 for offset-currentLength >= chunkSize { 383 // Insert a block a zero 384 headers, err := d.Conn.ObjectPut(d.Container, getSegment(), bytes.NewReader(zeroBuf), false, "", d.getContentType(), nil) 385 if err != nil { 386 if err == swift.ObjectNotFound { 387 return 0, storagedriver.PathNotFoundError{Path: getSegment()} 388 } 389 return 0, err 390 } 391 currentLength += chunkSize 392 partNumber++ 393 hash.Write([]byte(headers["Etag"])) 394 } 395 396 cursor = currentLength 397 paddingReader = bytes.NewReader(zeroBuf) 398 } else if offset-cursor > 0 { 399 // Offset is inside the current segment : we need to read the 400 // data from the beginning of the segment to offset 401 file, _, err := d.Conn.ObjectOpen(d.Container, getSegment(), false, nil) 402 if err != nil { 403 if err == swift.ObjectNotFound { 404 return 0, storagedriver.PathNotFoundError{Path: getSegment()} 405 } 406 return 0, err 407 } 408 defer file.Close() 409 paddingReader = file 410 } 411 412 readers := []io.Reader{} 413 if paddingReader != nil { 414 readers = append(readers, io.LimitReader(paddingReader, offset-cursor)) 415 } 416 readers = append(readers, io.LimitReader(reader, chunkSize-(offset-cursor))) 417 multi = io.MultiReader(readers...) 418 419 writeSegment := func(segment string) (finished bool, bytesRead int64, err error) { 420 currentSegment, err := d.Conn.ObjectCreate(d.Container, segment, false, "", d.getContentType(), nil) 421 if err != nil { 422 if err == swift.ObjectNotFound { 423 return false, bytesRead, storagedriver.PathNotFoundError{Path: segment} 424 } 425 return false, bytesRead, err 426 } 427 428 segmentHash := md5.New() 429 writer := io.MultiWriter(currentSegment, segmentHash) 430 431 n, err := io.Copy(writer, multi) 432 if err != nil { 433 return false, bytesRead, err 434 } 435 436 if n > 0 { 437 defer func() { 438 closeError := currentSegment.Close() 439 if err != nil { 440 err = closeError 441 } 442 hexHash := hex.EncodeToString(segmentHash.Sum(nil)) 443 hash.Write([]byte(hexHash)) 444 }() 445 bytesRead += n - max(0, offset-cursor) 446 } 447 448 if n < chunkSize { 449 // We wrote all the data 450 if cursor+n < currentLength { 451 // Copy the end of the chunk 452 headers := make(swift.Headers) 453 headers["Range"] = "bytes=" + strconv.FormatInt(cursor+n, 10) + "-" + strconv.FormatInt(cursor+chunkSize, 10) 454 file, _, err := d.Conn.ObjectOpen(d.Container, d.swiftPath(path), false, headers) 455 if err != nil { 456 if err == swift.ObjectNotFound { 457 return false, bytesRead, storagedriver.PathNotFoundError{Path: path} 458 } 459 return false, bytesRead, err 460 } 461 462 _, copyErr := io.Copy(writer, file) 463 464 if err := file.Close(); err != nil { 465 if err == swift.ObjectNotFound { 466 return false, bytesRead, storagedriver.PathNotFoundError{Path: path} 467 } 468 return false, bytesRead, err 469 } 470 471 if copyErr != nil { 472 return false, bytesRead, copyErr 473 } 474 } 475 476 return true, bytesRead, nil 477 } 478 479 multi = io.LimitReader(reader, chunkSize) 480 cursor += chunkSize 481 partNumber++ 482 483 return false, bytesRead, nil 484 } 485 486 finished := false 487 read := int64(0) 488 bytesRead := int64(0) 489 for finished == false { 490 finished, read, err = writeSegment(getSegment()) 491 bytesRead += read 492 if err != nil { 493 return bytesRead, err 494 } 495 } 496 497 for ; partNumber < len(segments); partNumber++ { 498 hash.Write([]byte(segments[partNumber].Hash)) 499 } 500 501 if createManifest { 502 if err := d.createManifest(path, d.Container+"/"+segmentPath); err != nil { 503 return 0, err 504 } 505 } 506 507 expectedHash := hex.EncodeToString(hash.Sum(nil)) 508 waitingTime := readAfterWriteWait 509 endTime := time.Now().Add(readAfterWriteTimeout) 510 for { 511 var infos swift.Object 512 if infos, _, err = d.Conn.Object(d.Container, d.swiftPath(path)); err == nil { 513 if strings.Trim(infos.Hash, "\"") == expectedHash { 514 return bytesRead, nil 515 } 516 err = fmt.Errorf("Timeout expired while waiting for segments of %s to show up", path) 517 } 518 if time.Now().Add(waitingTime).After(endTime) { 519 break 520 } 521 time.Sleep(waitingTime) 522 waitingTime *= 2 523 } 524 525 return bytesRead, err 526 } 527 528 // Stat retrieves the FileInfo for the given path, including the current size 529 // in bytes and the creation time. 530 func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) { 531 swiftPath := d.swiftPath(path) 532 opts := &swift.ObjectsOpts{ 533 Prefix: swiftPath, 534 Delimiter: '/', 535 } 536 537 objects, err := d.Conn.ObjectsAll(d.Container, opts) 538 if err != nil { 539 if err == swift.ContainerNotFound { 540 return nil, storagedriver.PathNotFoundError{Path: path} 541 } 542 return nil, err 543 } 544 545 fi := storagedriver.FileInfoFields{ 546 Path: strings.TrimPrefix(strings.TrimSuffix(swiftPath, "/"), d.swiftPath("/")), 547 } 548 549 for _, obj := range objects { 550 if obj.PseudoDirectory && obj.Name == swiftPath+"/" { 551 fi.IsDir = true 552 return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil 553 } else if obj.Name == swiftPath { 554 // On Swift 1.12, the 'bytes' field is always 0 555 // so we need to do a second HEAD request 556 info, _, err := d.Conn.Object(d.Container, swiftPath) 557 if err != nil { 558 if err == swift.ObjectNotFound { 559 return nil, storagedriver.PathNotFoundError{Path: path} 560 } 561 return nil, err 562 } 563 fi.IsDir = false 564 fi.Size = info.Bytes 565 fi.ModTime = info.LastModified 566 return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil 567 } 568 } 569 570 return nil, storagedriver.PathNotFoundError{Path: path} 571 } 572 573 // List returns a list of the objects that are direct descendants of the given path. 574 func (d *driver) List(ctx context.Context, path string) ([]string, error) { 575 var files []string 576 577 prefix := d.swiftPath(path) 578 if prefix != "" { 579 prefix += "/" 580 } 581 582 opts := &swift.ObjectsOpts{ 583 Prefix: prefix, 584 Delimiter: '/', 585 } 586 587 objects, err := d.Conn.ObjectsAll(d.Container, opts) 588 for _, obj := range objects { 589 files = append(files, strings.TrimPrefix(strings.TrimSuffix(obj.Name, "/"), d.swiftPath("/"))) 590 } 591 592 if err == swift.ContainerNotFound || (len(objects) == 0 && path != "/") { 593 return files, storagedriver.PathNotFoundError{Path: path} 594 } 595 return files, err 596 } 597 598 // Move moves an object stored at sourcePath to destPath, removing the original 599 // object. 600 func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error { 601 _, headers, err := d.Conn.Object(d.Container, d.swiftPath(sourcePath)) 602 if err == nil { 603 if manifest, ok := headers["X-Object-Manifest"]; ok { 604 if err = d.createManifest(destPath, manifest); err != nil { 605 return err 606 } 607 err = d.Conn.ObjectDelete(d.Container, d.swiftPath(sourcePath)) 608 } else { 609 err = d.Conn.ObjectMove(d.Container, d.swiftPath(sourcePath), d.Container, d.swiftPath(destPath)) 610 } 611 } 612 if err == swift.ObjectNotFound { 613 return storagedriver.PathNotFoundError{Path: sourcePath} 614 } 615 return err 616 } 617 618 // Delete recursively deletes all objects stored at "path" and its subpaths. 619 func (d *driver) Delete(ctx context.Context, path string) error { 620 opts := swift.ObjectsOpts{ 621 Prefix: d.swiftPath(path) + "/", 622 } 623 624 objects, err := d.Conn.ObjectsAll(d.Container, &opts) 625 if err != nil { 626 if err == swift.ContainerNotFound { 627 return storagedriver.PathNotFoundError{Path: path} 628 } 629 return err 630 } 631 632 for _, obj := range objects { 633 if obj.PseudoDirectory { 634 continue 635 } 636 if _, headers, err := d.Conn.Object(d.Container, obj.Name); err == nil { 637 manifest, ok := headers["X-Object-Manifest"] 638 if ok { 639 _, prefix := parseManifest(manifest) 640 segments, err := d.getAllSegments(prefix) 641 if err != nil { 642 return err 643 } 644 objects = append(objects, segments...) 645 } 646 } else { 647 if err == swift.ObjectNotFound { 648 return storagedriver.PathNotFoundError{Path: obj.Name} 649 } 650 return err 651 } 652 } 653 654 if d.BulkDeleteSupport && len(objects) > 0 { 655 filenames := make([]string, len(objects)) 656 for i, obj := range objects { 657 filenames[i] = obj.Name 658 } 659 _, err = d.Conn.BulkDelete(d.Container, filenames) 660 // Don't fail on ObjectNotFound because eventual consistency 661 // makes this situation normal. 662 if err != nil && err != swift.Forbidden && err != swift.ObjectNotFound { 663 if err == swift.ContainerNotFound { 664 return storagedriver.PathNotFoundError{Path: path} 665 } 666 return err 667 } 668 } else { 669 for _, obj := range objects { 670 if err := d.Conn.ObjectDelete(d.Container, obj.Name); err != nil { 671 if err == swift.ObjectNotFound { 672 return storagedriver.PathNotFoundError{Path: obj.Name} 673 } 674 return err 675 } 676 } 677 } 678 679 _, _, err = d.Conn.Object(d.Container, d.swiftPath(path)) 680 if err == nil { 681 if err := d.Conn.ObjectDelete(d.Container, d.swiftPath(path)); err != nil { 682 if err == swift.ObjectNotFound { 683 return storagedriver.PathNotFoundError{Path: path} 684 } 685 return err 686 } 687 } else if err == swift.ObjectNotFound { 688 if len(objects) == 0 { 689 return storagedriver.PathNotFoundError{Path: path} 690 } 691 } else { 692 return err 693 } 694 return nil 695 } 696 697 // URLFor returns a URL which may be used to retrieve the content stored at the given path. 698 func (d *driver) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) { 699 if d.SecretKey == "" { 700 return "", storagedriver.ErrUnsupportedMethod{} 701 } 702 703 methodString := "GET" 704 method, ok := options["method"] 705 if ok { 706 if methodString, ok = method.(string); !ok { 707 return "", storagedriver.ErrUnsupportedMethod{} 708 } 709 } 710 711 if methodString == "HEAD" { 712 // A "HEAD" request on a temporary URL is allowed if the 713 // signature was generated with "GET", "POST" or "PUT" 714 methodString = "GET" 715 } 716 717 supported := false 718 for _, method := range d.TempURLMethods { 719 if method == methodString { 720 supported = true 721 break 722 } 723 } 724 725 if !supported { 726 return "", storagedriver.ErrUnsupportedMethod{} 727 } 728 729 expiresTime := time.Now().Add(20 * time.Minute) 730 expires, ok := options["expiry"] 731 if ok { 732 et, ok := expires.(time.Time) 733 if ok { 734 expiresTime = et 735 } 736 } 737 738 tempURL := d.Conn.ObjectTempUrl(d.Container, d.swiftPath(path), d.SecretKey, methodString, expiresTime) 739 740 if d.AccessKey != "" { 741 // On HP Cloud, the signature must be in the form of tenant_id:access_key:signature 742 url, _ := url.Parse(tempURL) 743 query := url.Query() 744 query.Set("temp_url_sig", fmt.Sprintf("%s:%s:%s", d.Conn.TenantId, d.AccessKey, query.Get("temp_url_sig"))) 745 url.RawQuery = query.Encode() 746 tempURL = url.String() 747 } 748 749 return tempURL, nil 750 } 751 752 func (d *driver) swiftPath(path string) string { 753 return strings.TrimLeft(strings.TrimRight(d.Prefix+"/files"+path, "/"), "/") 754 } 755 756 func (d *driver) swiftSegmentPath(path string) (string, error) { 757 checksum := sha1.New() 758 random := make([]byte, 32) 759 if _, err := rand.Read(random); err != nil { 760 return "", err 761 } 762 path = hex.EncodeToString(checksum.Sum(append([]byte(path), random...))) 763 return strings.TrimLeft(strings.TrimRight(d.Prefix+"/segments/"+path[0:3]+"/"+path[3:], "/"), "/"), nil 764 } 765 766 func (d *driver) getContentType() string { 767 return "application/octet-stream" 768 } 769 770 func (d *driver) getAllSegments(path string) ([]swift.Object, error) { 771 segments, err := d.Conn.ObjectsAll(d.Container, &swift.ObjectsOpts{Prefix: path}) 772 if err == swift.ContainerNotFound { 773 return nil, storagedriver.PathNotFoundError{Path: path} 774 } 775 return segments, err 776 } 777 778 func (d *driver) createManifest(path string, segments string) error { 779 headers := make(swift.Headers) 780 headers["X-Object-Manifest"] = segments 781 manifest, err := d.Conn.ObjectCreate(d.Container, d.swiftPath(path), false, "", d.getContentType(), headers) 782 if err != nil { 783 if err == swift.ObjectNotFound { 784 return storagedriver.PathNotFoundError{Path: path} 785 } 786 return err 787 } 788 if err := manifest.Close(); err != nil { 789 if err == swift.ObjectNotFound { 790 return storagedriver.PathNotFoundError{Path: path} 791 } 792 return err 793 } 794 return nil 795 } 796 797 func parseManifest(manifest string) (container string, prefix string) { 798 components := strings.SplitN(manifest, "/", 2) 799 container = components[0] 800 if len(components) > 1 { 801 prefix = components[1] 802 } 803 return container, prefix 804 } 805 806 func generateSecret() (string, error) { 807 var secretBytes [32]byte 808 if _, err := rand.Read(secretBytes[:]); err != nil { 809 return "", fmt.Errorf("could not generate random bytes for Swift secret key: %v", err) 810 } 811 return hex.EncodeToString(secretBytes[:]), nil 812 }