diff options
Diffstat (limited to 'vendor/github.com/minio/minio-go/v7/api-put-object.go')
| -rw-r--r-- | vendor/github.com/minio/minio-go/v7/api-put-object.go | 473 |
1 files changed, 0 insertions, 473 deletions
diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object.go b/vendor/github.com/minio/minio-go/v7/api-put-object.go deleted file mode 100644 index bbd8924..0000000 --- a/vendor/github.com/minio/minio-go/v7/api-put-object.go +++ /dev/null | |||
| @@ -1,473 +0,0 @@ | |||
| 1 | /* | ||
| 2 | * MinIO Go Library for Amazon S3 Compatible Cloud Storage | ||
| 3 | * Copyright 2015-2017 MinIO, Inc. | ||
| 4 | * | ||
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | * you may not use this file except in compliance with the License. | ||
| 7 | * You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package minio | ||
| 19 | |||
| 20 | import ( | ||
| 21 | "bytes" | ||
| 22 | "context" | ||
| 23 | "encoding/base64" | ||
| 24 | "errors" | ||
| 25 | "fmt" | ||
| 26 | "hash/crc32" | ||
| 27 | "io" | ||
| 28 | "net/http" | ||
| 29 | "sort" | ||
| 30 | "time" | ||
| 31 | |||
| 32 | "github.com/minio/minio-go/v7/pkg/encrypt" | ||
| 33 | "github.com/minio/minio-go/v7/pkg/s3utils" | ||
| 34 | "golang.org/x/net/http/httpguts" | ||
| 35 | ) | ||
| 36 | |||
| 37 | // ReplicationStatus represents replication status of object | ||
| 38 | type ReplicationStatus string | ||
| 39 | |||
| 40 | const ( | ||
| 41 | // ReplicationStatusPending indicates replication is pending | ||
| 42 | ReplicationStatusPending ReplicationStatus = "PENDING" | ||
| 43 | // ReplicationStatusComplete indicates replication completed ok | ||
| 44 | ReplicationStatusComplete ReplicationStatus = "COMPLETED" | ||
| 45 | // ReplicationStatusFailed indicates replication failed | ||
| 46 | ReplicationStatusFailed ReplicationStatus = "FAILED" | ||
| 47 | // ReplicationStatusReplica indicates object is a replica of a source | ||
| 48 | ReplicationStatusReplica ReplicationStatus = "REPLICA" | ||
| 49 | ) | ||
| 50 | |||
| 51 | // Empty returns true if no replication status set. | ||
| 52 | func (r ReplicationStatus) Empty() bool { | ||
| 53 | return r == "" | ||
| 54 | } | ||
| 55 | |||
| 56 | // AdvancedPutOptions for internal use - to be utilized by replication, ILM transition | ||
| 57 | // implementation on MinIO server | ||
| 58 | type AdvancedPutOptions struct { | ||
| 59 | SourceVersionID string | ||
| 60 | SourceETag string | ||
| 61 | ReplicationStatus ReplicationStatus | ||
| 62 | SourceMTime time.Time | ||
| 63 | ReplicationRequest bool | ||
| 64 | RetentionTimestamp time.Time | ||
| 65 | TaggingTimestamp time.Time | ||
| 66 | LegalholdTimestamp time.Time | ||
| 67 | ReplicationValidityCheck bool | ||
| 68 | } | ||
| 69 | |||
| 70 | // PutObjectOptions represents options specified by user for PutObject call | ||
| 71 | type PutObjectOptions struct { | ||
| 72 | UserMetadata map[string]string | ||
| 73 | UserTags map[string]string | ||
| 74 | Progress io.Reader | ||
| 75 | ContentType string | ||
| 76 | ContentEncoding string | ||
| 77 | ContentDisposition string | ||
| 78 | ContentLanguage string | ||
| 79 | CacheControl string | ||
| 80 | Expires time.Time | ||
| 81 | Mode RetentionMode | ||
| 82 | RetainUntilDate time.Time | ||
| 83 | ServerSideEncryption encrypt.ServerSide | ||
| 84 | NumThreads uint | ||
| 85 | StorageClass string | ||
| 86 | WebsiteRedirectLocation string | ||
| 87 | PartSize uint64 | ||
| 88 | LegalHold LegalHoldStatus | ||
| 89 | SendContentMd5 bool | ||
| 90 | DisableContentSha256 bool | ||
| 91 | DisableMultipart bool | ||
| 92 | |||
| 93 | // ConcurrentStreamParts will create NumThreads buffers of PartSize bytes, | ||
| 94 | // fill them serially and upload them in parallel. | ||
| 95 | // This can be used for faster uploads on non-seekable or slow-to-seek input. | ||
| 96 | ConcurrentStreamParts bool | ||
| 97 | Internal AdvancedPutOptions | ||
| 98 | |||
| 99 | customHeaders http.Header | ||
| 100 | } | ||
| 101 | |||
| 102 | // SetMatchETag if etag matches while PUT MinIO returns an error | ||
| 103 | // this is a MinIO specific extension to support optimistic locking | ||
| 104 | // semantics. | ||
| 105 | func (opts *PutObjectOptions) SetMatchETag(etag string) { | ||
| 106 | if opts.customHeaders == nil { | ||
| 107 | opts.customHeaders = http.Header{} | ||
| 108 | } | ||
| 109 | opts.customHeaders.Set("If-Match", "\""+etag+"\"") | ||
| 110 | } | ||
| 111 | |||
| 112 | // SetMatchETagExcept if etag does not match while PUT MinIO returns an | ||
| 113 | // error this is a MinIO specific extension to support optimistic locking | ||
| 114 | // semantics. | ||
| 115 | func (opts *PutObjectOptions) SetMatchETagExcept(etag string) { | ||
| 116 | if opts.customHeaders == nil { | ||
| 117 | opts.customHeaders = http.Header{} | ||
| 118 | } | ||
| 119 | opts.customHeaders.Set("If-None-Match", "\""+etag+"\"") | ||
| 120 | } | ||
| 121 | |||
| 122 | // getNumThreads - gets the number of threads to be used in the multipart | ||
| 123 | // put object operation | ||
| 124 | func (opts PutObjectOptions) getNumThreads() (numThreads int) { | ||
| 125 | if opts.NumThreads > 0 { | ||
| 126 | numThreads = int(opts.NumThreads) | ||
| 127 | } else { | ||
| 128 | numThreads = totalWorkers | ||
| 129 | } | ||
| 130 | return | ||
| 131 | } | ||
| 132 | |||
| 133 | // Header - constructs the headers from metadata entered by user in | ||
| 134 | // PutObjectOptions struct | ||
| 135 | func (opts PutObjectOptions) Header() (header http.Header) { | ||
| 136 | header = make(http.Header) | ||
| 137 | |||
| 138 | contentType := opts.ContentType | ||
| 139 | if contentType == "" { | ||
| 140 | contentType = "application/octet-stream" | ||
| 141 | } | ||
| 142 | header.Set("Content-Type", contentType) | ||
| 143 | |||
| 144 | if opts.ContentEncoding != "" { | ||
| 145 | header.Set("Content-Encoding", opts.ContentEncoding) | ||
| 146 | } | ||
| 147 | if opts.ContentDisposition != "" { | ||
| 148 | header.Set("Content-Disposition", opts.ContentDisposition) | ||
| 149 | } | ||
| 150 | if opts.ContentLanguage != "" { | ||
| 151 | header.Set("Content-Language", opts.ContentLanguage) | ||
| 152 | } | ||
| 153 | if opts.CacheControl != "" { | ||
| 154 | header.Set("Cache-Control", opts.CacheControl) | ||
| 155 | } | ||
| 156 | |||
| 157 | if !opts.Expires.IsZero() { | ||
| 158 | header.Set("Expires", opts.Expires.UTC().Format(http.TimeFormat)) | ||
| 159 | } | ||
| 160 | |||
| 161 | if opts.Mode != "" { | ||
| 162 | header.Set(amzLockMode, opts.Mode.String()) | ||
| 163 | } | ||
| 164 | |||
| 165 | if !opts.RetainUntilDate.IsZero() { | ||
| 166 | header.Set("X-Amz-Object-Lock-Retain-Until-Date", opts.RetainUntilDate.Format(time.RFC3339)) | ||
| 167 | } | ||
| 168 | |||
| 169 | if opts.LegalHold != "" { | ||
| 170 | header.Set(amzLegalHoldHeader, opts.LegalHold.String()) | ||
| 171 | } | ||
| 172 | |||
| 173 | if opts.ServerSideEncryption != nil { | ||
| 174 | opts.ServerSideEncryption.Marshal(header) | ||
| 175 | } | ||
| 176 | |||
| 177 | if opts.StorageClass != "" { | ||
| 178 | header.Set(amzStorageClass, opts.StorageClass) | ||
| 179 | } | ||
| 180 | |||
| 181 | if opts.WebsiteRedirectLocation != "" { | ||
| 182 | header.Set(amzWebsiteRedirectLocation, opts.WebsiteRedirectLocation) | ||
| 183 | } | ||
| 184 | |||
| 185 | if !opts.Internal.ReplicationStatus.Empty() { | ||
| 186 | header.Set(amzBucketReplicationStatus, string(opts.Internal.ReplicationStatus)) | ||
| 187 | } | ||
| 188 | if !opts.Internal.SourceMTime.IsZero() { | ||
| 189 | header.Set(minIOBucketSourceMTime, opts.Internal.SourceMTime.Format(time.RFC3339Nano)) | ||
| 190 | } | ||
| 191 | if opts.Internal.SourceETag != "" { | ||
| 192 | header.Set(minIOBucketSourceETag, opts.Internal.SourceETag) | ||
| 193 | } | ||
| 194 | if opts.Internal.ReplicationRequest { | ||
| 195 | header.Set(minIOBucketReplicationRequest, "true") | ||
| 196 | } | ||
| 197 | if opts.Internal.ReplicationValidityCheck { | ||
| 198 | header.Set(minIOBucketReplicationCheck, "true") | ||
| 199 | } | ||
| 200 | if !opts.Internal.LegalholdTimestamp.IsZero() { | ||
| 201 | header.Set(minIOBucketReplicationObjectLegalHoldTimestamp, opts.Internal.LegalholdTimestamp.Format(time.RFC3339Nano)) | ||
| 202 | } | ||
| 203 | if !opts.Internal.RetentionTimestamp.IsZero() { | ||
| 204 | header.Set(minIOBucketReplicationObjectRetentionTimestamp, opts.Internal.RetentionTimestamp.Format(time.RFC3339Nano)) | ||
| 205 | } | ||
| 206 | if !opts.Internal.TaggingTimestamp.IsZero() { | ||
| 207 | header.Set(minIOBucketReplicationTaggingTimestamp, opts.Internal.TaggingTimestamp.Format(time.RFC3339Nano)) | ||
| 208 | } | ||
| 209 | |||
| 210 | if len(opts.UserTags) != 0 { | ||
| 211 | header.Set(amzTaggingHeader, s3utils.TagEncode(opts.UserTags)) | ||
| 212 | } | ||
| 213 | |||
| 214 | for k, v := range opts.UserMetadata { | ||
| 215 | if isAmzHeader(k) || isStandardHeader(k) || isStorageClassHeader(k) { | ||
| 216 | header.Set(k, v) | ||
| 217 | } else { | ||
| 218 | header.Set("x-amz-meta-"+k, v) | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | // set any other additional custom headers. | ||
| 223 | for k, v := range opts.customHeaders { | ||
| 224 | header[k] = v | ||
| 225 | } | ||
| 226 | |||
| 227 | return | ||
| 228 | } | ||
| 229 | |||
| 230 | // validate() checks if the UserMetadata map has standard headers or and raises an error if so. | ||
| 231 | func (opts PutObjectOptions) validate() (err error) { | ||
| 232 | for k, v := range opts.UserMetadata { | ||
| 233 | if !httpguts.ValidHeaderFieldName(k) || isStandardHeader(k) || isSSEHeader(k) || isStorageClassHeader(k) { | ||
| 234 | return errInvalidArgument(k + " unsupported user defined metadata name") | ||
| 235 | } | ||
| 236 | if !httpguts.ValidHeaderFieldValue(v) { | ||
| 237 | return errInvalidArgument(v + " unsupported user defined metadata value") | ||
| 238 | } | ||
| 239 | } | ||
| 240 | if opts.Mode != "" && !opts.Mode.IsValid() { | ||
| 241 | return errInvalidArgument(opts.Mode.String() + " unsupported retention mode") | ||
| 242 | } | ||
| 243 | if opts.LegalHold != "" && !opts.LegalHold.IsValid() { | ||
| 244 | return errInvalidArgument(opts.LegalHold.String() + " unsupported legal-hold status") | ||
| 245 | } | ||
| 246 | return nil | ||
| 247 | } | ||
| 248 | |||
| 249 | // completedParts is a collection of parts sortable by their part numbers. | ||
| 250 | // used for sorting the uploaded parts before completing the multipart request. | ||
| 251 | type completedParts []CompletePart | ||
| 252 | |||
| 253 | func (a completedParts) Len() int { return len(a) } | ||
| 254 | func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } | ||
| 255 | func (a completedParts) Less(i, j int) bool { return a[i].PartNumber < a[j].PartNumber } | ||
| 256 | |||
| 257 | // PutObject creates an object in a bucket. | ||
| 258 | // | ||
| 259 | // You must have WRITE permissions on a bucket to create an object. | ||
| 260 | // | ||
| 261 | // - For size smaller than 16MiB PutObject automatically does a | ||
| 262 | // single atomic PUT operation. | ||
| 263 | // | ||
| 264 | // - For size larger than 16MiB PutObject automatically does a | ||
| 265 | // multipart upload operation. | ||
| 266 | // | ||
| 267 | // - For size input as -1 PutObject does a multipart Put operation | ||
| 268 | // until input stream reaches EOF. Maximum object size that can | ||
| 269 | // be uploaded through this operation will be 5TiB. | ||
| 270 | // | ||
| 271 | // WARNING: Passing down '-1' will use memory and these cannot | ||
| 272 | // be reused for best outcomes for PutObject(), pass the size always. | ||
| 273 | // | ||
| 274 | // NOTE: Upon errors during upload multipart operation is entirely aborted. | ||
| 275 | func (c *Client) PutObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, | ||
| 276 | opts PutObjectOptions, | ||
| 277 | ) (info UploadInfo, err error) { | ||
| 278 | if objectSize < 0 && opts.DisableMultipart { | ||
| 279 | return UploadInfo{}, errors.New("object size must be provided with disable multipart upload") | ||
| 280 | } | ||
| 281 | |||
| 282 | err = opts.validate() | ||
| 283 | if err != nil { | ||
| 284 | return UploadInfo{}, err | ||
| 285 | } | ||
| 286 | |||
| 287 | return c.putObjectCommon(ctx, bucketName, objectName, reader, objectSize, opts) | ||
| 288 | } | ||
| 289 | |||
| 290 | func (c *Client) putObjectCommon(ctx context.Context, bucketName, objectName string, reader io.Reader, size int64, opts PutObjectOptions) (info UploadInfo, err error) { | ||
| 291 | // Check for largest object size allowed. | ||
| 292 | if size > int64(maxMultipartPutObjectSize) { | ||
| 293 | return UploadInfo{}, errEntityTooLarge(size, maxMultipartPutObjectSize, bucketName, objectName) | ||
| 294 | } | ||
| 295 | |||
| 296 | // NOTE: Streaming signature is not supported by GCS. | ||
| 297 | if s3utils.IsGoogleEndpoint(*c.endpointURL) { | ||
| 298 | return c.putObject(ctx, bucketName, objectName, reader, size, opts) | ||
| 299 | } | ||
| 300 | |||
| 301 | partSize := opts.PartSize | ||
| 302 | if opts.PartSize == 0 { | ||
| 303 | partSize = minPartSize | ||
| 304 | } | ||
| 305 | |||
| 306 | if c.overrideSignerType.IsV2() { | ||
| 307 | if size >= 0 && size < int64(partSize) || opts.DisableMultipart { | ||
| 308 | return c.putObject(ctx, bucketName, objectName, reader, size, opts) | ||
| 309 | } | ||
| 310 | return c.putObjectMultipart(ctx, bucketName, objectName, reader, size, opts) | ||
| 311 | } | ||
| 312 | |||
| 313 | if size < 0 { | ||
| 314 | if opts.DisableMultipart { | ||
| 315 | return UploadInfo{}, errors.New("no length provided and multipart disabled") | ||
| 316 | } | ||
| 317 | if opts.ConcurrentStreamParts && opts.NumThreads > 1 { | ||
| 318 | return c.putObjectMultipartStreamParallel(ctx, bucketName, objectName, reader, opts) | ||
| 319 | } | ||
| 320 | return c.putObjectMultipartStreamNoLength(ctx, bucketName, objectName, reader, opts) | ||
| 321 | } | ||
| 322 | |||
| 323 | if size < int64(partSize) || opts.DisableMultipart { | ||
| 324 | return c.putObject(ctx, bucketName, objectName, reader, size, opts) | ||
| 325 | } | ||
| 326 | |||
| 327 | return c.putObjectMultipartStream(ctx, bucketName, objectName, reader, size, opts) | ||
| 328 | } | ||
| 329 | |||
| 330 | func (c *Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketName, objectName string, reader io.Reader, opts PutObjectOptions) (info UploadInfo, err error) { | ||
| 331 | // Input validation. | ||
| 332 | if err = s3utils.CheckValidBucketName(bucketName); err != nil { | ||
| 333 | return UploadInfo{}, err | ||
| 334 | } | ||
| 335 | if err = s3utils.CheckValidObjectName(objectName); err != nil { | ||
| 336 | return UploadInfo{}, err | ||
| 337 | } | ||
| 338 | |||
| 339 | // Total data read and written to server. should be equal to | ||
| 340 | // 'size' at the end of the call. | ||
| 341 | var totalUploadedSize int64 | ||
| 342 | |||
| 343 | // Complete multipart upload. | ||
| 344 | var complMultipartUpload completeMultipartUpload | ||
| 345 | |||
| 346 | // Calculate the optimal parts info for a given size. | ||
| 347 | totalPartsCount, partSize, _, err := OptimalPartInfo(-1, opts.PartSize) | ||
| 348 | if err != nil { | ||
| 349 | return UploadInfo{}, err | ||
| 350 | } | ||
| 351 | |||
| 352 | if !opts.SendContentMd5 { | ||
| 353 | if opts.UserMetadata == nil { | ||
| 354 | opts.UserMetadata = make(map[string]string, 1) | ||
| 355 | } | ||
| 356 | opts.UserMetadata["X-Amz-Checksum-Algorithm"] = "CRC32C" | ||
| 357 | } | ||
| 358 | |||
| 359 | // Initiate a new multipart upload. | ||
| 360 | uploadID, err := c.newUploadID(ctx, bucketName, objectName, opts) | ||
| 361 | if err != nil { | ||
| 362 | return UploadInfo{}, err | ||
| 363 | } | ||
| 364 | delete(opts.UserMetadata, "X-Amz-Checksum-Algorithm") | ||
| 365 | |||
| 366 | defer func() { | ||
| 367 | if err != nil { | ||
| 368 | c.abortMultipartUpload(ctx, bucketName, objectName, uploadID) | ||
| 369 | } | ||
| 370 | }() | ||
| 371 | |||
| 372 | // Part number always starts with '1'. | ||
| 373 | partNumber := 1 | ||
| 374 | |||
| 375 | // Initialize parts uploaded map. | ||
| 376 | partsInfo := make(map[int]ObjectPart) | ||
| 377 | |||
| 378 | // Create a buffer. | ||
| 379 | buf := make([]byte, partSize) | ||
| 380 | |||
| 381 | // Create checksums | ||
| 382 | // CRC32C is ~50% faster on AMD64 @ 30GB/s | ||
| 383 | var crcBytes []byte | ||
| 384 | customHeader := make(http.Header) | ||
| 385 | crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) | ||
| 386 | |||
| 387 | for partNumber <= totalPartsCount { | ||
| 388 | length, rerr := readFull(reader, buf) | ||
| 389 | if rerr == io.EOF && partNumber > 1 { | ||
| 390 | break | ||
| 391 | } | ||
| 392 | |||
| 393 | if rerr != nil && rerr != io.ErrUnexpectedEOF && rerr != io.EOF { | ||
| 394 | return UploadInfo{}, rerr | ||
| 395 | } | ||
| 396 | |||
| 397 | var md5Base64 string | ||
| 398 | if opts.SendContentMd5 { | ||
| 399 | // Calculate md5sum. | ||
| 400 | hash := c.md5Hasher() | ||
| 401 | hash.Write(buf[:length]) | ||
| 402 | md5Base64 = base64.StdEncoding.EncodeToString(hash.Sum(nil)) | ||
| 403 | hash.Close() | ||
| 404 | } else { | ||
| 405 | crc.Reset() | ||
| 406 | crc.Write(buf[:length]) | ||
| 407 | cSum := crc.Sum(nil) | ||
| 408 | customHeader.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(cSum)) | ||
| 409 | crcBytes = append(crcBytes, cSum...) | ||
| 410 | } | ||
| 411 | |||
| 412 | // Update progress reader appropriately to the latest offset | ||
| 413 | // as we read from the source. | ||
| 414 | rd := newHook(bytes.NewReader(buf[:length]), opts.Progress) | ||
| 415 | |||
| 416 | // Proceed to upload the part. | ||
| 417 | p := uploadPartParams{bucketName: bucketName, objectName: objectName, uploadID: uploadID, reader: rd, partNumber: partNumber, md5Base64: md5Base64, size: int64(length), sse: opts.ServerSideEncryption, streamSha256: !opts.DisableContentSha256, customHeader: customHeader} | ||
| 418 | objPart, uerr := c.uploadPart(ctx, p) | ||
| 419 | if uerr != nil { | ||
| 420 | return UploadInfo{}, uerr | ||
| 421 | } | ||
| 422 | |||
| 423 | // Save successfully uploaded part metadata. | ||
| 424 | partsInfo[partNumber] = objPart | ||
| 425 | |||
| 426 | // Save successfully uploaded size. | ||
| 427 | totalUploadedSize += int64(length) | ||
| 428 | |||
| 429 | // Increment part number. | ||
| 430 | partNumber++ | ||
| 431 | |||
| 432 | // For unknown size, Read EOF we break away. | ||
| 433 | // We do not have to upload till totalPartsCount. | ||
| 434 | if rerr == io.EOF { | ||
| 435 | break | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | // Loop over total uploaded parts to save them in | ||
| 440 | // Parts array before completing the multipart request. | ||
| 441 | for i := 1; i < partNumber; i++ { | ||
| 442 | part, ok := partsInfo[i] | ||
| 443 | if !ok { | ||
| 444 | return UploadInfo{}, errInvalidArgument(fmt.Sprintf("Missing part number %d", i)) | ||
| 445 | } | ||
| 446 | complMultipartUpload.Parts = append(complMultipartUpload.Parts, CompletePart{ | ||
| 447 | ETag: part.ETag, | ||
| 448 | PartNumber: part.PartNumber, | ||
| 449 | ChecksumCRC32: part.ChecksumCRC32, | ||
| 450 | ChecksumCRC32C: part.ChecksumCRC32C, | ||
| 451 | ChecksumSHA1: part.ChecksumSHA1, | ||
| 452 | ChecksumSHA256: part.ChecksumSHA256, | ||
| 453 | }) | ||
| 454 | } | ||
| 455 | |||
| 456 | // Sort all completed parts. | ||
| 457 | sort.Sort(completedParts(complMultipartUpload.Parts)) | ||
| 458 | |||
| 459 | opts = PutObjectOptions{} | ||
| 460 | if len(crcBytes) > 0 { | ||
| 461 | // Add hash of hashes. | ||
| 462 | crc.Reset() | ||
| 463 | crc.Write(crcBytes) | ||
| 464 | opts.UserMetadata = map[string]string{"X-Amz-Checksum-Crc32c": base64.StdEncoding.EncodeToString(crc.Sum(nil))} | ||
| 465 | } | ||
| 466 | uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, opts) | ||
| 467 | if err != nil { | ||
| 468 | return UploadInfo{}, err | ||
| 469 | } | ||
| 470 | |||
| 471 | uploadInfo.Size = totalUploadedSize | ||
| 472 | return uploadInfo, nil | ||
| 473 | } | ||