aws-sdk-s3 1.13.0 → 1.23.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aws-sdk-s3/client.rb +262 -181
- data/lib/aws-sdk-s3/client_api.rb +33 -2
- data/lib/aws-sdk-s3/customizations/bucket.rb +2 -2
- data/lib/aws-sdk-s3/customizations/object.rb +60 -0
- data/lib/aws-sdk-s3/customizations/object_summary.rb +7 -0
- data/lib/aws-sdk-s3/customizations.rb +1 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +9 -3
- data/lib/aws-sdk-s3/file_downloader.rb +10 -9
- data/lib/aws-sdk-s3/file_part.rb +5 -7
- data/lib/aws-sdk-s3/file_uploader.rb +1 -3
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +160 -0
- data/lib/aws-sdk-s3/object.rb +4 -1
- data/lib/aws-sdk-s3/object_summary.rb +4 -1
- data/lib/aws-sdk-s3/plugins/md5s.rb +1 -1
- data/lib/aws-sdk-s3/plugins/redirects.rb +3 -1
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +5 -1
- data/lib/aws-sdk-s3/presigner.rb +1 -0
- data/lib/aws-sdk-s3/types.rb +296 -29
- data/lib/aws-sdk-s3.rb +1 -1
- metadata +5 -4
@@ -21,6 +21,7 @@ module Aws::S3
|
|
21
21
|
AccessControlPolicy = Shapes::StructureShape.new(name: 'AccessControlPolicy')
|
22
22
|
AccessControlTranslation = Shapes::StructureShape.new(name: 'AccessControlTranslation')
|
23
23
|
AccountId = Shapes::StringShape.new(name: 'AccountId')
|
24
|
+
AllowQuotedRecordDelimiter = Shapes::BooleanShape.new(name: 'AllowQuotedRecordDelimiter')
|
24
25
|
AllowedHeader = Shapes::StringShape.new(name: 'AllowedHeader')
|
25
26
|
AllowedHeaders = Shapes::ListShape.new(name: 'AllowedHeaders', flattened: true)
|
26
27
|
AllowedMethod = Shapes::StringShape.new(name: 'AllowedMethod')
|
@@ -117,6 +118,8 @@ module Aws::S3
|
|
117
118
|
DeleteBucketWebsiteRequest = Shapes::StructureShape.new(name: 'DeleteBucketWebsiteRequest')
|
118
119
|
DeleteMarker = Shapes::BooleanShape.new(name: 'DeleteMarker')
|
119
120
|
DeleteMarkerEntry = Shapes::StructureShape.new(name: 'DeleteMarkerEntry')
|
121
|
+
DeleteMarkerReplication = Shapes::StructureShape.new(name: 'DeleteMarkerReplication')
|
122
|
+
DeleteMarkerReplicationStatus = Shapes::StringShape.new(name: 'DeleteMarkerReplicationStatus')
|
120
123
|
DeleteMarkerVersionId = Shapes::StringShape.new(name: 'DeleteMarkerVersionId')
|
121
124
|
DeleteMarkers = Shapes::ListShape.new(name: 'DeleteMarkers', flattened: true)
|
122
125
|
DeleteObjectOutput = Shapes::StructureShape.new(name: 'DeleteObjectOutput')
|
@@ -335,6 +338,7 @@ module Aws::S3
|
|
335
338
|
OutputSerialization = Shapes::StructureShape.new(name: 'OutputSerialization')
|
336
339
|
Owner = Shapes::StructureShape.new(name: 'Owner')
|
337
340
|
OwnerOverride = Shapes::StringShape.new(name: 'OwnerOverride')
|
341
|
+
ParquetInput = Shapes::StructureShape.new(name: 'ParquetInput')
|
338
342
|
Part = Shapes::StructureShape.new(name: 'Part')
|
339
343
|
PartNumber = Shapes::IntegerShape.new(name: 'PartNumber')
|
340
344
|
PartNumberMarker = Shapes::IntegerShape.new(name: 'PartNumberMarker')
|
@@ -344,6 +348,7 @@ module Aws::S3
|
|
344
348
|
Permission = Shapes::StringShape.new(name: 'Permission')
|
345
349
|
Policy = Shapes::StringShape.new(name: 'Policy')
|
346
350
|
Prefix = Shapes::StringShape.new(name: 'Prefix')
|
351
|
+
Priority = Shapes::IntegerShape.new(name: 'Priority')
|
347
352
|
Progress = Shapes::StructureShape.new(name: 'Progress')
|
348
353
|
ProgressEvent = Shapes::StructureShape.new(name: 'ProgressEvent')
|
349
354
|
Protocol = Shapes::StringShape.new(name: 'Protocol')
|
@@ -389,6 +394,8 @@ module Aws::S3
|
|
389
394
|
ReplicaKmsKeyID = Shapes::StringShape.new(name: 'ReplicaKmsKeyID')
|
390
395
|
ReplicationConfiguration = Shapes::StructureShape.new(name: 'ReplicationConfiguration')
|
391
396
|
ReplicationRule = Shapes::StructureShape.new(name: 'ReplicationRule')
|
397
|
+
ReplicationRuleAndOperator = Shapes::StructureShape.new(name: 'ReplicationRuleAndOperator')
|
398
|
+
ReplicationRuleFilter = Shapes::StructureShape.new(name: 'ReplicationRuleFilter')
|
392
399
|
ReplicationRuleStatus = Shapes::StringShape.new(name: 'ReplicationRuleStatus')
|
393
400
|
ReplicationRules = Shapes::ListShape.new(name: 'ReplicationRules', flattened: true)
|
394
401
|
ReplicationStatus = Shapes::StringShape.new(name: 'ReplicationStatus')
|
@@ -558,6 +565,7 @@ module Aws::S3
|
|
558
565
|
CSVInput.add_member(:record_delimiter, Shapes::ShapeRef.new(shape: RecordDelimiter, location_name: "RecordDelimiter"))
|
559
566
|
CSVInput.add_member(:field_delimiter, Shapes::ShapeRef.new(shape: FieldDelimiter, location_name: "FieldDelimiter"))
|
560
567
|
CSVInput.add_member(:quote_character, Shapes::ShapeRef.new(shape: QuoteCharacter, location_name: "QuoteCharacter"))
|
568
|
+
CSVInput.add_member(:allow_quoted_record_delimiter, Shapes::ShapeRef.new(shape: AllowQuotedRecordDelimiter, location_name: "AllowQuotedRecordDelimiter"))
|
561
569
|
CSVInput.struct_class = Types::CSVInput
|
562
570
|
|
563
571
|
CSVOutput.add_member(:quote_fields, Shapes::ShapeRef.new(shape: QuoteFields, location_name: "QuoteFields"))
|
@@ -772,6 +780,9 @@ module Aws::S3
|
|
772
780
|
DeleteMarkerEntry.add_member(:last_modified, Shapes::ShapeRef.new(shape: LastModified, location_name: "LastModified"))
|
773
781
|
DeleteMarkerEntry.struct_class = Types::DeleteMarkerEntry
|
774
782
|
|
783
|
+
DeleteMarkerReplication.add_member(:status, Shapes::ShapeRef.new(shape: DeleteMarkerReplicationStatus, location_name: "Status"))
|
784
|
+
DeleteMarkerReplication.struct_class = Types::DeleteMarkerReplication
|
785
|
+
|
775
786
|
DeleteMarkers.member = Shapes::ShapeRef.new(shape: DeleteMarkerEntry)
|
776
787
|
|
777
788
|
DeleteObjectOutput.add_member(:delete_marker, Shapes::ShapeRef.new(shape: DeleteMarker, location: "header", location_name: "x-amz-delete-marker"))
|
@@ -1135,6 +1146,7 @@ module Aws::S3
|
|
1135
1146
|
InputSerialization.add_member(:csv, Shapes::ShapeRef.new(shape: CSVInput, location_name: "CSV"))
|
1136
1147
|
InputSerialization.add_member(:compression_type, Shapes::ShapeRef.new(shape: CompressionType, location_name: "CompressionType"))
|
1137
1148
|
InputSerialization.add_member(:json, Shapes::ShapeRef.new(shape: JSONInput, location_name: "JSON"))
|
1149
|
+
InputSerialization.add_member(:parquet, Shapes::ShapeRef.new(shape: ParquetInput, location_name: "Parquet"))
|
1138
1150
|
InputSerialization.struct_class = Types::InputSerialization
|
1139
1151
|
|
1140
1152
|
InventoryConfiguration.add_member(:destination, Shapes::ShapeRef.new(shape: InventoryDestination, required: true, location_name: "Destination"))
|
@@ -1463,6 +1475,8 @@ module Aws::S3
|
|
1463
1475
|
Owner.add_member(:id, Shapes::ShapeRef.new(shape: ID, location_name: "ID"))
|
1464
1476
|
Owner.struct_class = Types::Owner
|
1465
1477
|
|
1478
|
+
ParquetInput.struct_class = Types::ParquetInput
|
1479
|
+
|
1466
1480
|
Part.add_member(:part_number, Shapes::ShapeRef.new(shape: PartNumber, location_name: "PartNumber"))
|
1467
1481
|
Part.add_member(:last_modified, Shapes::ShapeRef.new(shape: LastModified, location_name: "LastModified"))
|
1468
1482
|
Part.add_member(:etag, Shapes::ShapeRef.new(shape: ETag, location_name: "ETag"))
|
@@ -1714,12 +1728,24 @@ module Aws::S3
|
|
1714
1728
|
ReplicationConfiguration.struct_class = Types::ReplicationConfiguration
|
1715
1729
|
|
1716
1730
|
ReplicationRule.add_member(:id, Shapes::ShapeRef.new(shape: ID, location_name: "ID"))
|
1717
|
-
ReplicationRule.add_member(:
|
1731
|
+
ReplicationRule.add_member(:priority, Shapes::ShapeRef.new(shape: Priority, location_name: "Priority"))
|
1732
|
+
ReplicationRule.add_member(:prefix, Shapes::ShapeRef.new(shape: Prefix, deprecated: true, location_name: "Prefix"))
|
1733
|
+
ReplicationRule.add_member(:filter, Shapes::ShapeRef.new(shape: ReplicationRuleFilter, location_name: "Filter"))
|
1718
1734
|
ReplicationRule.add_member(:status, Shapes::ShapeRef.new(shape: ReplicationRuleStatus, required: true, location_name: "Status"))
|
1719
1735
|
ReplicationRule.add_member(:source_selection_criteria, Shapes::ShapeRef.new(shape: SourceSelectionCriteria, location_name: "SourceSelectionCriteria"))
|
1720
1736
|
ReplicationRule.add_member(:destination, Shapes::ShapeRef.new(shape: Destination, required: true, location_name: "Destination"))
|
1737
|
+
ReplicationRule.add_member(:delete_marker_replication, Shapes::ShapeRef.new(shape: DeleteMarkerReplication, location_name: "DeleteMarkerReplication"))
|
1721
1738
|
ReplicationRule.struct_class = Types::ReplicationRule
|
1722
1739
|
|
1740
|
+
ReplicationRuleAndOperator.add_member(:prefix, Shapes::ShapeRef.new(shape: Prefix, location_name: "Prefix"))
|
1741
|
+
ReplicationRuleAndOperator.add_member(:tags, Shapes::ShapeRef.new(shape: TagSet, location_name: "Tag", metadata: {"flattened"=>true}))
|
1742
|
+
ReplicationRuleAndOperator.struct_class = Types::ReplicationRuleAndOperator
|
1743
|
+
|
1744
|
+
ReplicationRuleFilter.add_member(:prefix, Shapes::ShapeRef.new(shape: Prefix, location_name: "Prefix"))
|
1745
|
+
ReplicationRuleFilter.add_member(:tag, Shapes::ShapeRef.new(shape: Tag, location_name: "Tag"))
|
1746
|
+
ReplicationRuleFilter.add_member(:and, Shapes::ShapeRef.new(shape: ReplicationRuleAndOperator, location_name: "And"))
|
1747
|
+
ReplicationRuleFilter.struct_class = Types::ReplicationRuleFilter
|
1748
|
+
|
1723
1749
|
ReplicationRules.member = Shapes::ShapeRef.new(shape: ReplicationRule)
|
1724
1750
|
|
1725
1751
|
RequestPaymentConfiguration.add_member(:payer, Shapes::ShapeRef.new(shape: Payer, required: true, location_name: "Payer"))
|
@@ -1957,10 +1983,15 @@ module Aws::S3
|
|
1957
1983
|
api.version = "2006-03-01"
|
1958
1984
|
|
1959
1985
|
api.metadata = {
|
1986
|
+
"apiVersion" => "2006-03-01",
|
1987
|
+
"checksumFormat" => "md5",
|
1960
1988
|
"endpointPrefix" => "s3",
|
1989
|
+
"globalEndpoint" => "s3.amazonaws.com",
|
1961
1990
|
"protocol" => "rest-xml",
|
1991
|
+
"serviceAbbreviation" => "Amazon S3",
|
1962
1992
|
"serviceFullName" => "Amazon Simple Storage Service",
|
1963
|
-
"
|
1993
|
+
"serviceId" => "S3",
|
1994
|
+
"uid" => "s3-2006-03-01",
|
1964
1995
|
}
|
1965
1996
|
|
1966
1997
|
api.add_operation(:abort_multipart_upload, Seahorse::Model::Operation.new.tap do |o|
|
@@ -61,8 +61,8 @@ module Aws
|
|
61
61
|
# You can pass `virtual_host: true` to use the bucket name as the
|
62
62
|
# host name.
|
63
63
|
#
|
64
|
-
# bucket = s3.bucket('my.bucket.com'
|
65
|
-
# bucket.url
|
64
|
+
# bucket = s3.bucket('my.bucket.com')
|
65
|
+
# bucket.url(virtual_host: true)
|
66
66
|
# #=> "http://my.bucket.com"
|
67
67
|
#
|
68
68
|
# @option options [Boolean] :virtual_host (false) When `true`,
|
@@ -214,6 +214,62 @@ module Aws
|
|
214
214
|
url.to_s
|
215
215
|
end
|
216
216
|
|
217
|
+
# Uploads a stream in a streaming fashion to the current object in S3.
|
218
|
+
#
|
219
|
+
# # Passed chunks automatically split into multipart upload parts
|
220
|
+
# # and the parts are uploaded in parallel. This allows for streaming uploads
|
221
|
+
# # that never touch the disk.
|
222
|
+
#
|
223
|
+
# Note that this is known to have issues in JRuby until jruby-9.1.15.0, so avoid using this with older versions of JRuby.
|
224
|
+
#
|
225
|
+
# @example Streaming chunks of data
|
226
|
+
# obj.upload_stream do |write_stream|
|
227
|
+
# 10.times { write_stream << 'foo' }
|
228
|
+
# end
|
229
|
+
# @example Streaming chunks of data
|
230
|
+
# obj.upload_stream do |write_stream|
|
231
|
+
# IO.copy_stream(IO.popen('ls'), write_stream)
|
232
|
+
# end
|
233
|
+
# @example Streaming chunks of data
|
234
|
+
# obj.upload_stream do |write_stream|
|
235
|
+
# IO.copy_stream(STDIN, write_stream)
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# @option options [Integer] :thread_count
|
239
|
+
# The number of parallel multipart uploads
|
240
|
+
# Default `:thread_count` is `10`.
|
241
|
+
#
|
242
|
+
# @option options [Boolean] :tempfile
|
243
|
+
# Normally read data is stored in memory when building the parts in order to complete
|
244
|
+
# the underlying multipart upload. By passing `:tempfile => true` data read will be
|
245
|
+
# temporarily stored on disk reducing the memory footprint vastly.
|
246
|
+
# Default `:tempfile` is `false`.
|
247
|
+
#
|
248
|
+
# @option options [Integer] :part_size
|
249
|
+
# Define how big each part size but the last should be.
|
250
|
+
# Default `:part_size` is `5 * 1024 * 1024`.
|
251
|
+
#
|
252
|
+
# @raise [MultipartUploadError] If an object is being uploaded in
|
253
|
+
# parts, and the upload can not be completed, then the upload is
|
254
|
+
# aborted and this error is raised. The raised error has a `#errors`
|
255
|
+
# method that returns the failures that caused the upload to be
|
256
|
+
# aborted.
|
257
|
+
#
|
258
|
+
# @return [Boolean] Returns `true` when the object is uploaded
|
259
|
+
# without any errors.
|
260
|
+
#
|
261
|
+
def upload_stream(options = {}, &block)
|
262
|
+
uploading_options = options.dup
|
263
|
+
uploader = MultipartStreamUploader.new(
|
264
|
+
client: client,
|
265
|
+
thread_count: uploading_options.delete(:thread_count),
|
266
|
+
tempfile: uploading_options.delete(:tempfile),
|
267
|
+
part_size: uploading_options.delete(:part_size),
|
268
|
+
)
|
269
|
+
uploader.upload(uploading_options.merge(bucket: bucket_name, key: key), &block)
|
270
|
+
true
|
271
|
+
end
|
272
|
+
|
217
273
|
# Uploads a file from disk to the current object in S3.
|
218
274
|
#
|
219
275
|
# # small files are uploaded in a single API call
|
@@ -277,6 +333,10 @@ module Aws
|
|
277
333
|
# @option options [String] thread_count Customize threads used in multipart
|
278
334
|
# download, if not provided, 10 is default value
|
279
335
|
#
|
336
|
+
# @option options [String] version_id The object version id used to retrieve
|
337
|
+
# the object, to know more about object versioning, see:
|
338
|
+
# https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
|
339
|
+
#
|
280
340
|
# @return [Boolean] Returns `true` when the file is downloaded
|
281
341
|
# without any errors.
|
282
342
|
def download_file(destination, options = {})
|
@@ -60,6 +60,13 @@ module Aws
|
|
60
60
|
object.upload_file(source, options)
|
61
61
|
end
|
62
62
|
|
63
|
+
# @options (see Object#upload_stream)
|
64
|
+
# @return (see Object#upload_stream)
|
65
|
+
# @see Object#upload_stream
|
66
|
+
def upload_stream(options = {}, &block)
|
67
|
+
object.upload_stream(options, &block)
|
68
|
+
end
|
69
|
+
|
63
70
|
# @param (see Object#download_file)
|
64
71
|
# @options (see Object#download_file)
|
65
72
|
# @return (see Object#download_file)
|
@@ -6,6 +6,7 @@ require 'aws-sdk-s3/file_uploader'
|
|
6
6
|
require 'aws-sdk-s3/file_downloader'
|
7
7
|
require 'aws-sdk-s3/legacy_signer'
|
8
8
|
require 'aws-sdk-s3/multipart_file_uploader'
|
9
|
+
require 'aws-sdk-s3/multipart_stream_uploader'
|
9
10
|
require 'aws-sdk-s3/multipart_upload_error'
|
10
11
|
require 'aws-sdk-s3/object_copier'
|
11
12
|
require 'aws-sdk-s3/object_multipart_copier'
|
@@ -22,8 +22,10 @@ module Aws
|
|
22
22
|
|
23
23
|
def write(chunk)
|
24
24
|
chunk = truncate_chunk(chunk)
|
25
|
-
|
26
|
-
|
25
|
+
if chunk.bytesize > 0
|
26
|
+
@bytes_written += chunk.bytesize
|
27
|
+
@decrypter.write(chunk)
|
28
|
+
end
|
27
29
|
end
|
28
30
|
|
29
31
|
def finalize
|
@@ -39,8 +41,12 @@ module Aws
|
|
39
41
|
def truncate_chunk(chunk)
|
40
42
|
if chunk.bytesize + @bytes_written <= @max_bytes
|
41
43
|
chunk
|
42
|
-
|
44
|
+
elsif @bytes_written < @max_bytes
|
43
45
|
chunk[0..(@max_bytes - @bytes_written - 1)]
|
46
|
+
else
|
47
|
+
# If the tag was sent over after the full body has been read,
|
48
|
+
# we don't want to accidentally append it.
|
49
|
+
""
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
@@ -24,15 +24,18 @@ module Aws
|
|
24
24
|
@mode = options[:mode] || "auto"
|
25
25
|
@thread_count = options[:thread_count] || THREAD_COUNT
|
26
26
|
@chunk_size = options[:chunk_size]
|
27
|
-
@
|
28
|
-
|
27
|
+
@params = {
|
28
|
+
bucket: options[:bucket],
|
29
|
+
key: options[:key],
|
30
|
+
}
|
31
|
+
@params[:version_id] = options[:version_id] if options[:version_id]
|
29
32
|
|
30
33
|
case @mode
|
31
34
|
when "auto" then multipart_download
|
32
35
|
when "single_request" then single_request
|
33
36
|
when "get_range"
|
34
37
|
if @chunk_size
|
35
|
-
resp = @client.head_object(
|
38
|
+
resp = @client.head_object(@params)
|
36
39
|
multithreaded_get_by_ranges(construct_chunks(resp.content_length))
|
37
40
|
else
|
38
41
|
msg = "In :get_range mode, :chunk_size must be provided"
|
@@ -48,7 +51,7 @@ module Aws
|
|
48
51
|
private
|
49
52
|
|
50
53
|
def multipart_download
|
51
|
-
resp = @client.head_object(
|
54
|
+
resp = @client.head_object(@params.merge(part_number: 1))
|
52
55
|
count = resp.parts_count
|
53
56
|
if count.nil? || count <= 1
|
54
57
|
resp.content_length < MIN_CHUNK_SIZE ?
|
@@ -56,7 +59,7 @@ module Aws
|
|
56
59
|
multithreaded_get_by_ranges(construct_chunks(resp.content_length))
|
57
60
|
else
|
58
61
|
# partNumber is an option
|
59
|
-
resp = @client.head_object(
|
62
|
+
resp = @client.head_object(@params)
|
60
63
|
resp.content_length < MIN_CHUNK_SIZE ?
|
61
64
|
single_request :
|
62
65
|
compute_mode(resp.content_length, count)
|
@@ -112,9 +115,7 @@ module Aws
|
|
112
115
|
batch.each do |chunk|
|
113
116
|
threads << Thread.new do
|
114
117
|
resp = @client.get_object(
|
115
|
-
|
116
|
-
:key => @key,
|
117
|
-
param.to_sym => chunk
|
118
|
+
@params.merge(param.to_sym => chunk)
|
118
119
|
)
|
119
120
|
write(resp)
|
120
121
|
end
|
@@ -131,7 +132,7 @@ module Aws
|
|
131
132
|
|
132
133
|
def single_request
|
133
134
|
@client.get_object(
|
134
|
-
|
135
|
+
@params.merge(response_target: @path)
|
135
136
|
)
|
136
137
|
end
|
137
138
|
end
|
data/lib/aws-sdk-s3/file_part.rb
CHANGED
@@ -56,14 +56,12 @@ module Aws
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def read_from_file(bytes, output_buffer)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
else
|
63
|
-
data = @file.read(remaining_bytes)
|
64
|
-
end
|
59
|
+
length = [remaining_bytes, *bytes].min
|
60
|
+
data = @file.read(length, output_buffer)
|
61
|
+
|
65
62
|
@position += data ? data.bytesize : 0
|
66
|
-
|
63
|
+
|
64
|
+
data.to_s unless bytes && (data.nil? || data.empty?)
|
67
65
|
end
|
68
66
|
|
69
67
|
def remaining_bytes
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'set'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module Aws
|
7
|
+
module S3
|
8
|
+
# @api private
|
9
|
+
class MultipartStreamUploader
|
10
|
+
# api private
|
11
|
+
PART_SIZE = 5 * 1024 * 1024 # 5MB
|
12
|
+
|
13
|
+
# api private
|
14
|
+
THREAD_COUNT = 10
|
15
|
+
|
16
|
+
# api private
|
17
|
+
TEMPFILE_PREIX = 'aws-sdk-s3-upload_stream'.freeze
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
CREATE_OPTIONS =
|
21
|
+
Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
UPLOAD_PART_OPTIONS =
|
25
|
+
Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
26
|
+
|
27
|
+
# @option options [Client] :client
|
28
|
+
def initialize(options = {})
|
29
|
+
@client = options[:client] || Client.new
|
30
|
+
@tempfile = options[:tempfile]
|
31
|
+
@part_size = options[:part_size] || PART_SIZE
|
32
|
+
@thread_count = options[:thread_count] || THREAD_COUNT
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Client]
|
36
|
+
attr_reader :client
|
37
|
+
|
38
|
+
# @option options [required,String] :bucket
|
39
|
+
# @option options [required,String] :key
|
40
|
+
# @return [void]
|
41
|
+
def upload(options = {}, &block)
|
42
|
+
upload_id = initiate_upload(options)
|
43
|
+
parts = upload_parts(upload_id, options, &block)
|
44
|
+
complete_upload(upload_id, parts, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def initiate_upload(options)
|
50
|
+
@client.create_multipart_upload(create_opts(options)).upload_id
|
51
|
+
end
|
52
|
+
|
53
|
+
def complete_upload(upload_id, parts, options)
|
54
|
+
@client.complete_multipart_upload(
|
55
|
+
bucket: options[:bucket],
|
56
|
+
key: options[:key],
|
57
|
+
upload_id: upload_id,
|
58
|
+
multipart_upload: { parts: parts })
|
59
|
+
end
|
60
|
+
|
61
|
+
def upload_parts(upload_id, options, &block)
|
62
|
+
completed = Queue.new
|
63
|
+
errors = IO.pipe do |read_pipe, write_pipe|
|
64
|
+
threads = upload_in_threads(read_pipe, completed, upload_part_opts(options).merge(upload_id: upload_id))
|
65
|
+
block.call(write_pipe)
|
66
|
+
write_pipe.close
|
67
|
+
threads.map(&:value).compact
|
68
|
+
end
|
69
|
+
if errors.empty?
|
70
|
+
Array.new(completed.size) { completed.pop }.sort_by { |part| part[:part_number] }
|
71
|
+
else
|
72
|
+
abort_upload(upload_id, options, errors)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def abort_upload(upload_id, options, errors)
|
77
|
+
@client.abort_multipart_upload(
|
78
|
+
bucket: options[:bucket],
|
79
|
+
key: options[:key],
|
80
|
+
upload_id: upload_id
|
81
|
+
)
|
82
|
+
msg = "multipart upload failed: #{errors.map(&:message).join("; ")}"
|
83
|
+
raise MultipartUploadError.new(msg, errors)
|
84
|
+
rescue MultipartUploadError => error
|
85
|
+
raise error
|
86
|
+
rescue => error
|
87
|
+
msg = "failed to abort multipart upload: #{error.message}"
|
88
|
+
raise MultipartUploadError.new(msg, errors + [error])
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_opts(options)
|
92
|
+
CREATE_OPTIONS.inject({}) do |hash, key|
|
93
|
+
hash[key] = options[key] if options.key?(key)
|
94
|
+
hash
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def upload_part_opts(options)
|
99
|
+
UPLOAD_PART_OPTIONS.inject({}) do |hash, key|
|
100
|
+
hash[key] = options[key] if options.key?(key)
|
101
|
+
hash
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def read_to_part_body(read_pipe)
|
106
|
+
return if read_pipe.closed?
|
107
|
+
temp_io = @tempfile ? Tempfile.new(TEMPFILE_PREIX) : StringIO.new
|
108
|
+
temp_io.binmode
|
109
|
+
bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
|
110
|
+
temp_io.rewind
|
111
|
+
if bytes_copied == 0
|
112
|
+
if Tempfile === temp_io
|
113
|
+
temp_io.close
|
114
|
+
temp_io.unlink
|
115
|
+
end
|
116
|
+
nil
|
117
|
+
else
|
118
|
+
temp_io
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def upload_in_threads(read_pipe, completed, options)
|
123
|
+
mutex = Mutex.new
|
124
|
+
part_number = 0
|
125
|
+
@thread_count.times.map do
|
126
|
+
thread = Thread.new do
|
127
|
+
begin
|
128
|
+
loop do
|
129
|
+
body, thread_part_number = mutex.synchronize do
|
130
|
+
[read_to_part_body(read_pipe), part_number += 1]
|
131
|
+
end
|
132
|
+
break unless (body || thread_part_number == 1)
|
133
|
+
begin
|
134
|
+
part = options.merge(
|
135
|
+
body: body,
|
136
|
+
part_number: thread_part_number,
|
137
|
+
)
|
138
|
+
resp = @client.upload_part(part)
|
139
|
+
completed << {etag: resp.etag, part_number: part[:part_number]}
|
140
|
+
ensure
|
141
|
+
if Tempfile === body
|
142
|
+
body.close
|
143
|
+
body.unlink
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
nil
|
148
|
+
rescue => error
|
149
|
+
# keep other threads from uploading other parts
|
150
|
+
mutex.synchronize { read_pipe.close_read }
|
151
|
+
error
|
152
|
+
end
|
153
|
+
end
|
154
|
+
thread.abort_on_exception = true
|
155
|
+
thread
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/aws-sdk-s3/object.rb
CHANGED
@@ -898,11 +898,14 @@ module Aws::S3
|
|
898
898
|
# record_delimiter: "RecordDelimiter",
|
899
899
|
# field_delimiter: "FieldDelimiter",
|
900
900
|
# quote_character: "QuoteCharacter",
|
901
|
+
# allow_quoted_record_delimiter: false,
|
901
902
|
# },
|
902
|
-
# compression_type: "NONE", # accepts NONE, GZIP
|
903
|
+
# compression_type: "NONE", # accepts NONE, GZIP, BZIP2
|
903
904
|
# json: {
|
904
905
|
# type: "DOCUMENT", # accepts DOCUMENT, LINES
|
905
906
|
# },
|
907
|
+
# parquet: {
|
908
|
+
# },
|
906
909
|
# },
|
907
910
|
# expression_type: "SQL", # required, accepts SQL
|
908
911
|
# expression: "Expression", # required
|
@@ -741,11 +741,14 @@ module Aws::S3
|
|
741
741
|
# record_delimiter: "RecordDelimiter",
|
742
742
|
# field_delimiter: "FieldDelimiter",
|
743
743
|
# quote_character: "QuoteCharacter",
|
744
|
+
# allow_quoted_record_delimiter: false,
|
744
745
|
# },
|
745
|
-
# compression_type: "NONE", # accepts NONE, GZIP
|
746
|
+
# compression_type: "NONE", # accepts NONE, GZIP, BZIP2
|
746
747
|
# json: {
|
747
748
|
# type: "DOCUMENT", # accepts DOCUMENT, LINES
|
748
749
|
# },
|
750
|
+
# parquet: {
|
751
|
+
# },
|
749
752
|
# },
|
750
753
|
# expression_type: "SQL", # required, accepts SQL
|
751
754
|
# expression: "Expression", # required
|
@@ -18,7 +18,9 @@ by Amazon S3.
|
|
18
18
|
response = @handler.call(context)
|
19
19
|
if context.http_response.status_code == 307
|
20
20
|
endpoint = context.http_response.headers['location']
|
21
|
-
context.http_request.endpoint
|
21
|
+
unless context.http_request.endpoint.host.include?('fips')
|
22
|
+
context.http_request.endpoint = endpoint
|
23
|
+
end
|
22
24
|
context.http_response.body.truncate(0)
|
23
25
|
@handler.call(context)
|
24
26
|
else
|
@@ -113,7 +113,7 @@ module Aws
|
|
113
113
|
private
|
114
114
|
|
115
115
|
def handle_region_errors(response)
|
116
|
-
if wrong_sigv4_region?(response)
|
116
|
+
if wrong_sigv4_region?(response) && !fips_region?(response)
|
117
117
|
get_region_and_retry(response.context)
|
118
118
|
else
|
119
119
|
response
|
@@ -133,6 +133,10 @@ module Aws
|
|
133
133
|
S3::BUCKET_REGIONS[context.params[:bucket]] = actual_region
|
134
134
|
end
|
135
135
|
|
136
|
+
def fips_region?(resp)
|
137
|
+
resp.context.http_request.endpoint.host.include?('fips')
|
138
|
+
end
|
139
|
+
|
136
140
|
def wrong_sigv4_region?(resp)
|
137
141
|
resp.context.http_response.status_code == 400 &&
|
138
142
|
(
|
data/lib/aws-sdk-s3/presigner.rb
CHANGED