aws-sdk-s3 1.142.0 → 1.208.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +417 -1
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/access_grants_credentials.rb +57 -0
- data/lib/aws-sdk-s3/access_grants_credentials_provider.rb +250 -0
- data/lib/aws-sdk-s3/bucket.rb +358 -106
- data/lib/aws-sdk-s3/bucket_acl.rb +10 -9
- data/lib/aws-sdk-s3/bucket_cors.rb +10 -9
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +11 -6
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +64 -7
- data/lib/aws-sdk-s3/bucket_logging.rb +5 -5
- data/lib/aws-sdk-s3/bucket_notification.rb +3 -3
- data/lib/aws-sdk-s3/bucket_policy.rb +14 -13
- data/lib/aws-sdk-s3/bucket_region_cache.rb +9 -5
- data/lib/aws-sdk-s3/bucket_request_payment.rb +6 -6
- data/lib/aws-sdk-s3/bucket_tagging.rb +7 -7
- data/lib/aws-sdk-s3/bucket_versioning.rb +47 -14
- data/lib/aws-sdk-s3/bucket_website.rb +7 -7
- data/lib/aws-sdk-s3/client.rb +5671 -2195
- data/lib/aws-sdk-s3/client_api.rb +665 -166
- data/lib/aws-sdk-s3/customizations/bucket.rb +1 -1
- data/lib/aws-sdk-s3/customizations/errors.rb +15 -2
- data/lib/aws-sdk-s3/customizations/object.rb +87 -91
- data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
- data/lib/aws-sdk-s3/customizations/object_version.rb +13 -0
- data/lib/aws-sdk-s3/customizations.rb +28 -36
- data/lib/aws-sdk-s3/default_executor.rb +103 -0
- data/lib/aws-sdk-s3/encryption/client.rb +4 -4
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +2 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +4 -2
- data/lib/aws-sdk-s3/encryptionV2/client.rb +100 -25
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +7 -162
- data/lib/aws-sdk-s3/encryptionV2/decryption.rb +205 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +17 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +2 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +10 -2
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +5 -0
- data/lib/aws-sdk-s3/encryptionV3/client.rb +885 -0
- data/lib/aws-sdk-s3/encryptionV3/decrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/decryption.rb +244 -0
- data/lib/aws-sdk-s3/encryptionV3/default_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/default_key_provider.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/encrypt_handler.rb +98 -0
- data/lib/aws-sdk-s3/encryptionV3/errors.rb +47 -0
- data/lib/aws-sdk-s3/encryptionV3/io_auth_decrypter.rb +60 -0
- data/lib/aws-sdk-s3/encryptionV3/io_decrypter.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV3/io_encrypter.rb +84 -0
- data/lib/aws-sdk-s3/encryptionV3/key_provider.rb +28 -0
- data/lib/aws-sdk-s3/encryptionV3/kms_cipher_provider.rb +159 -0
- data/lib/aws-sdk-s3/encryptionV3/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV3/utils.rb +321 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +1 -0
- data/lib/aws-sdk-s3/encryption_v3.rb +24 -0
- data/lib/aws-sdk-s3/endpoint_parameters.rb +37 -34
- data/lib/aws-sdk-s3/endpoint_provider.rb +572 -277
- data/lib/aws-sdk-s3/endpoints.rb +566 -1612
- data/lib/aws-sdk-s3/errors.rb +58 -0
- data/lib/aws-sdk-s3/express_credentials_provider.rb +27 -4
- data/lib/aws-sdk-s3/file_downloader.rb +192 -146
- data/lib/aws-sdk-s3/file_uploader.rb +10 -14
- data/lib/aws-sdk-s3/legacy_signer.rb +2 -1
- data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +106 -102
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +99 -108
- data/lib/aws-sdk-s3/multipart_upload.rb +112 -12
- data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
- data/lib/aws-sdk-s3/multipart_upload_part.rb +55 -39
- data/lib/aws-sdk-s3/object.rb +713 -227
- data/lib/aws-sdk-s3/object_acl.rb +15 -9
- data/lib/aws-sdk-s3/object_copier.rb +1 -1
- data/lib/aws-sdk-s3/object_multipart_copier.rb +12 -9
- data/lib/aws-sdk-s3/object_summary.rb +592 -173
- data/lib/aws-sdk-s3/object_version.rb +102 -17
- data/lib/aws-sdk-s3/plugins/access_grants.rb +178 -0
- data/lib/aws-sdk-s3/plugins/checksum_algorithm.rb +31 -0
- data/lib/aws-sdk-s3/plugins/endpoints.rb +26 -213
- data/lib/aws-sdk-s3/plugins/express_session_auth.rb +19 -21
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -18
- data/lib/aws-sdk-s3/plugins/md5s.rb +10 -71
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +7 -2
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +5 -7
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
- data/lib/aws-sdk-s3/presigner.rb +7 -5
- data/lib/aws-sdk-s3/resource.rb +53 -20
- data/lib/aws-sdk-s3/transfer_manager.rb +303 -0
- data/lib/aws-sdk-s3/types.rb +4738 -1542
- data/lib/aws-sdk-s3.rb +35 -31
- data/sig/bucket.rbs +231 -0
- data/sig/bucket_acl.rbs +78 -0
- data/sig/bucket_cors.rbs +69 -0
- data/sig/bucket_lifecycle.rbs +88 -0
- data/sig/bucket_lifecycle_configuration.rbs +115 -0
- data/sig/bucket_logging.rbs +76 -0
- data/sig/bucket_notification.rbs +114 -0
- data/sig/bucket_policy.rbs +59 -0
- data/sig/bucket_request_payment.rbs +54 -0
- data/sig/bucket_tagging.rbs +65 -0
- data/sig/bucket_versioning.rbs +77 -0
- data/sig/bucket_website.rbs +93 -0
- data/sig/client.rbs +2612 -0
- data/sig/customizations/bucket.rbs +19 -0
- data/sig/customizations/object.rbs +38 -0
- data/sig/customizations/object_summary.rbs +35 -0
- data/sig/errors.rbs +44 -0
- data/sig/multipart_upload.rbs +120 -0
- data/sig/multipart_upload_part.rbs +109 -0
- data/sig/object.rbs +464 -0
- data/sig/object_acl.rbs +86 -0
- data/sig/object_summary.rbs +347 -0
- data/sig/object_version.rbs +143 -0
- data/sig/resource.rbs +141 -0
- data/sig/types.rbs +2899 -0
- data/sig/waiters.rbs +95 -0
- metadata +58 -13
- data/lib/aws-sdk-s3/express_credentials_cache.rb +0 -30
- data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +0 -31
|
@@ -7,34 +7,21 @@ module Aws
|
|
|
7
7
|
module S3
|
|
8
8
|
# @api private
|
|
9
9
|
class MultipartFileUploader
|
|
10
|
-
|
|
11
10
|
MIN_PART_SIZE = 5 * 1024 * 1024 # 5MB
|
|
12
|
-
|
|
13
|
-
FILE_TOO_SMALL = "unable to multipart upload files smaller than 5MB"
|
|
14
|
-
|
|
15
11
|
MAX_PARTS = 10_000
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
COMPLETE_OPTIONS = Set.new(
|
|
25
|
-
Client.api.operation(:complete_multipart_upload).input.shape.member_names
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# @api private
|
|
29
|
-
UPLOAD_PART_OPTIONS = Set.new(
|
|
30
|
-
Client.api.operation(:upload_part).input.shape.member_names
|
|
12
|
+
CREATE_OPTIONS = Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
|
|
13
|
+
COMPLETE_OPTIONS = Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
|
|
14
|
+
UPLOAD_PART_OPTIONS = Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
|
15
|
+
CHECKSUM_KEYS = Set.new(
|
|
16
|
+
Client.api.operation(:upload_part).input.shape.members.map do |n, s|
|
|
17
|
+
n if s.location == 'header' && s.location_name.start_with?('x-amz-checksum-')
|
|
18
|
+
end.compact
|
|
31
19
|
)
|
|
32
20
|
|
|
33
21
|
# @option options [Client] :client
|
|
34
|
-
# @option options [Integer] :thread_count (THREAD_COUNT)
|
|
35
22
|
def initialize(options = {})
|
|
36
23
|
@client = options[:client] || Client.new
|
|
37
|
-
@
|
|
24
|
+
@executor = options[:executor]
|
|
38
25
|
end
|
|
39
26
|
|
|
40
27
|
# @return [Client]
|
|
@@ -48,13 +35,12 @@ module Aws
|
|
|
48
35
|
# It will be invoked with [bytes_read], [total_sizes]
|
|
49
36
|
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
|
50
37
|
def upload(source, options = {})
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
38
|
+
file_size = File.size(source)
|
|
39
|
+
raise ArgumentError, 'unable to multipart upload files smaller than 5MB' if file_size < MIN_PART_SIZE
|
|
40
|
+
|
|
41
|
+
upload_id = initiate_upload(options)
|
|
42
|
+
parts = upload_parts(upload_id, source, file_size, options)
|
|
43
|
+
complete_upload(upload_id, parts, file_size, options)
|
|
58
44
|
end
|
|
59
45
|
|
|
60
46
|
private
|
|
@@ -63,19 +49,21 @@ module Aws
|
|
|
63
49
|
@client.create_multipart_upload(create_opts(options)).upload_id
|
|
64
50
|
end
|
|
65
51
|
|
|
66
|
-
def complete_upload(upload_id, parts, options)
|
|
52
|
+
def complete_upload(upload_id, parts, file_size, options)
|
|
67
53
|
@client.complete_multipart_upload(
|
|
68
|
-
**complete_opts(options)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
**complete_opts(options),
|
|
55
|
+
upload_id: upload_id,
|
|
56
|
+
multipart_upload: { parts: parts },
|
|
57
|
+
mpu_object_size: file_size
|
|
72
58
|
)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
abort_upload(upload_id, options, [e])
|
|
73
61
|
end
|
|
74
62
|
|
|
75
|
-
def upload_parts(upload_id, source, options)
|
|
76
|
-
pending = PartList.new(compute_parts(upload_id, source, options))
|
|
63
|
+
def upload_parts(upload_id, source, file_size, options)
|
|
77
64
|
completed = PartList.new
|
|
78
|
-
|
|
65
|
+
pending = PartList.new(compute_parts(upload_id, source, file_size, options))
|
|
66
|
+
errors = upload_with_executor(pending, completed, options)
|
|
79
67
|
if errors.empty?
|
|
80
68
|
completed.to_a.sort_by { |part| part[:part_number] }
|
|
81
69
|
else
|
|
@@ -84,34 +72,30 @@ module Aws
|
|
|
84
72
|
end
|
|
85
73
|
|
|
86
74
|
def abort_upload(upload_id, options, errors)
|
|
87
|
-
@client.abort_multipart_upload(
|
|
88
|
-
|
|
89
|
-
key: options[:key],
|
|
90
|
-
upload_id: upload_id
|
|
91
|
-
)
|
|
92
|
-
msg = "multipart upload failed: #{errors.map(&:message).join("; ")}"
|
|
75
|
+
@client.abort_multipart_upload(bucket: options[:bucket], key: options[:key], upload_id: upload_id)
|
|
76
|
+
msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
93
77
|
raise MultipartUploadError.new(msg, errors)
|
|
94
|
-
rescue MultipartUploadError =>
|
|
95
|
-
raise
|
|
96
|
-
rescue =>
|
|
97
|
-
msg = "failed to abort multipart upload: #{
|
|
98
|
-
|
|
78
|
+
rescue MultipartUploadError => e
|
|
79
|
+
raise e
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
msg = "failed to abort multipart upload: #{e.message}. " \
|
|
82
|
+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
83
|
+
raise MultipartUploadError.new(msg, errors + [e])
|
|
99
84
|
end
|
|
100
85
|
|
|
101
|
-
def compute_parts(upload_id, source, options)
|
|
102
|
-
|
|
103
|
-
default_part_size = compute_default_part_size(size)
|
|
86
|
+
def compute_parts(upload_id, source, file_size, options)
|
|
87
|
+
default_part_size = compute_default_part_size(file_size)
|
|
104
88
|
offset = 0
|
|
105
89
|
part_number = 1
|
|
106
90
|
parts = []
|
|
107
|
-
while offset <
|
|
91
|
+
while offset < file_size
|
|
108
92
|
parts << upload_part_opts(options).merge(
|
|
109
93
|
upload_id: upload_id,
|
|
110
94
|
part_number: part_number,
|
|
111
95
|
body: FilePart.new(
|
|
112
96
|
source: source,
|
|
113
97
|
offset: offset,
|
|
114
|
-
size: part_size(
|
|
98
|
+
size: part_size(file_size, default_part_size, offset)
|
|
115
99
|
)
|
|
116
100
|
)
|
|
117
101
|
part_number += 1
|
|
@@ -120,68 +104,79 @@ module Aws
|
|
|
120
104
|
parts
|
|
121
105
|
end
|
|
122
106
|
|
|
107
|
+
def checksum_key?(key)
|
|
108
|
+
CHECKSUM_KEYS.include?(key)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def has_checksum_key?(keys)
|
|
112
|
+
keys.any? { |key| checksum_key?(key) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def checksum_not_required?(options)
|
|
116
|
+
@client.config.request_checksum_calculation == 'when_required' && !options[:checksum_algorithm]
|
|
117
|
+
end
|
|
118
|
+
|
|
123
119
|
def create_opts(options)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
opts = {}
|
|
121
|
+
unless checksum_not_required?(options)
|
|
122
|
+
opts[:checksum_algorithm] = Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM
|
|
127
123
|
end
|
|
124
|
+
opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
|
|
125
|
+
CREATE_OPTIONS.each_with_object(opts) { |k, h| h[k] = options[k] if options.key?(k) }
|
|
128
126
|
end
|
|
129
127
|
|
|
130
128
|
def complete_opts(options)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
129
|
+
opts = {}
|
|
130
|
+
opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
|
|
131
|
+
COMPLETE_OPTIONS.each_with_object(opts) { |k, h| h[k] = options[k] if options.key?(k) }
|
|
135
132
|
end
|
|
136
133
|
|
|
137
134
|
def upload_part_opts(options)
|
|
138
|
-
UPLOAD_PART_OPTIONS.
|
|
139
|
-
|
|
140
|
-
hash
|
|
135
|
+
UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
|
|
136
|
+
# don't pass through checksum calculations
|
|
137
|
+
hash[key] = options[key] if options.key?(key) && !checksum_key?(key)
|
|
141
138
|
end
|
|
142
139
|
end
|
|
143
140
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
completed.push(completed_part)
|
|
170
|
-
end
|
|
171
|
-
nil
|
|
172
|
-
rescue => error
|
|
173
|
-
# keep other threads from uploading other parts
|
|
174
|
-
pending.clear!
|
|
175
|
-
error
|
|
176
|
-
end
|
|
141
|
+
def upload_with_executor(pending, completed, options)
|
|
142
|
+
upload_attempts = 0
|
|
143
|
+
completion_queue = Queue.new
|
|
144
|
+
abort_upload = false
|
|
145
|
+
errors = []
|
|
146
|
+
progress = MultipartProgress.new(pending, options[:progress_callback])
|
|
147
|
+
|
|
148
|
+
while (part = pending.shift)
|
|
149
|
+
break if abort_upload
|
|
150
|
+
|
|
151
|
+
upload_attempts += 1
|
|
152
|
+
@executor.post(part) do |p|
|
|
153
|
+
update_progress(progress, p)
|
|
154
|
+
resp = @client.upload_part(p)
|
|
155
|
+
p[:body].close
|
|
156
|
+
completed_part = { etag: resp.etag, part_number: p[:part_number] }
|
|
157
|
+
apply_part_checksum(resp, completed_part)
|
|
158
|
+
completed.push(completed_part)
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
abort_upload = true
|
|
161
|
+
errors << e
|
|
162
|
+
ensure
|
|
163
|
+
completion_queue << :done
|
|
177
164
|
end
|
|
178
|
-
threads << thread
|
|
179
165
|
end
|
|
180
|
-
|
|
166
|
+
|
|
167
|
+
upload_attempts.times { completion_queue.pop }
|
|
168
|
+
errors
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def apply_part_checksum(resp, part)
|
|
172
|
+
return unless (checksum = resp.context.params[:checksum_algorithm])
|
|
173
|
+
|
|
174
|
+
k = :"checksum_#{checksum.downcase}"
|
|
175
|
+
part[k] = resp.send(k)
|
|
181
176
|
end
|
|
182
177
|
|
|
183
|
-
def compute_default_part_size(
|
|
184
|
-
[(
|
|
178
|
+
def compute_default_part_size(file_size)
|
|
179
|
+
[(file_size.to_f / MAX_PARTS).ceil, MIN_PART_SIZE].max.to_i
|
|
185
180
|
end
|
|
186
181
|
|
|
187
182
|
def part_size(total_size, part_size, offset)
|
|
@@ -192,9 +187,17 @@ module Aws
|
|
|
192
187
|
end
|
|
193
188
|
end
|
|
194
189
|
|
|
190
|
+
def update_progress(progress, part)
|
|
191
|
+
return unless progress.progress_callback
|
|
192
|
+
|
|
193
|
+
part[:on_chunk_sent] =
|
|
194
|
+
proc do |_chunk, bytes, _total|
|
|
195
|
+
progress.call(part[:part_number], bytes)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
195
199
|
# @api private
|
|
196
200
|
class PartList
|
|
197
|
-
|
|
198
201
|
def initialize(parts = [])
|
|
199
202
|
@parts = parts
|
|
200
203
|
@mutex = Mutex.new
|
|
@@ -223,7 +226,6 @@ module Aws
|
|
|
223
226
|
def to_a
|
|
224
227
|
@mutex.synchronize { @parts.dup }
|
|
225
228
|
end
|
|
226
|
-
|
|
227
229
|
end
|
|
228
230
|
|
|
229
231
|
# @api private
|
|
@@ -234,6 +236,8 @@ module Aws
|
|
|
234
236
|
@progress_callback = progress_callback
|
|
235
237
|
end
|
|
236
238
|
|
|
239
|
+
attr_reader :progress_callback
|
|
240
|
+
|
|
237
241
|
def call(part_number, bytes_read)
|
|
238
242
|
# part numbers start at 1
|
|
239
243
|
@bytes_sent[part_number - 1] = bytes_read
|
|
@@ -242,4 +246,4 @@ module Aws
|
|
|
242
246
|
end
|
|
243
247
|
end
|
|
244
248
|
end
|
|
245
|
-
end
|
|
249
|
+
end
|
|
@@ -9,33 +9,18 @@ module Aws
|
|
|
9
9
|
module S3
|
|
10
10
|
# @api private
|
|
11
11
|
class MultipartStreamUploader
|
|
12
|
-
# api private
|
|
13
|
-
PART_SIZE = 5 * 1024 * 1024 # 5MB
|
|
14
12
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
TEMPFILE_PREIX = 'aws-sdk-s3-upload_stream'.freeze
|
|
20
|
-
|
|
21
|
-
# @api private
|
|
22
|
-
CREATE_OPTIONS =
|
|
23
|
-
Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
|
|
24
|
-
|
|
25
|
-
# @api private
|
|
26
|
-
UPLOAD_PART_OPTIONS =
|
|
27
|
-
Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
|
28
|
-
|
|
29
|
-
# @api private
|
|
30
|
-
COMPLETE_UPLOAD_OPTIONS =
|
|
31
|
-
Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
|
|
13
|
+
DEFAULT_PART_SIZE = 5 * 1024 * 1024 # 5MB
|
|
14
|
+
CREATE_OPTIONS = Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
|
|
15
|
+
UPLOAD_PART_OPTIONS = Set.new(Client.api.operation(:upload_part).input.shape.member_names)
|
|
16
|
+
COMPLETE_UPLOAD_OPTIONS = Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
|
|
32
17
|
|
|
33
18
|
# @option options [Client] :client
|
|
34
19
|
def initialize(options = {})
|
|
35
20
|
@client = options[:client] || Client.new
|
|
21
|
+
@executor = options[:executor]
|
|
36
22
|
@tempfile = options[:tempfile]
|
|
37
|
-
@part_size = options[:part_size] ||
|
|
38
|
-
@thread_count = options[:thread_count] || THREAD_COUNT
|
|
23
|
+
@part_size = options[:part_size] || DEFAULT_PART_SIZE
|
|
39
24
|
end
|
|
40
25
|
|
|
41
26
|
# @return [Client]
|
|
@@ -45,7 +30,7 @@ module Aws
|
|
|
45
30
|
# @option options [required,String] :key
|
|
46
31
|
# @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
|
|
47
32
|
def upload(options = {}, &block)
|
|
48
|
-
Aws::Plugins::UserAgent.
|
|
33
|
+
Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
|
|
49
34
|
upload_id = initiate_upload(options)
|
|
50
35
|
parts = upload_parts(upload_id, options, &block)
|
|
51
36
|
complete_upload(upload_id, parts, options)
|
|
@@ -60,85 +45,80 @@ module Aws
|
|
|
60
45
|
|
|
61
46
|
def complete_upload(upload_id, parts, options)
|
|
62
47
|
@client.complete_multipart_upload(
|
|
63
|
-
**complete_opts(options).merge(
|
|
64
|
-
upload_id: upload_id,
|
|
65
|
-
multipart_upload: { parts: parts }
|
|
66
|
-
)
|
|
48
|
+
**complete_opts(options).merge(upload_id: upload_id, multipart_upload: { parts: parts })
|
|
67
49
|
)
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
abort_upload(upload_id, options, [e])
|
|
68
52
|
end
|
|
69
53
|
|
|
70
54
|
def upload_parts(upload_id, options, &block)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
55
|
+
completed_parts = Queue.new
|
|
56
|
+
errors = []
|
|
57
|
+
|
|
58
|
+
begin
|
|
74
59
|
IO.pipe do |read_pipe, write_pipe|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Ensure the pipe is closed to avoid https://github.com/jruby/jruby/issues/6111
|
|
83
|
-
write_pipe.close
|
|
60
|
+
upload_thread = Thread.new do
|
|
61
|
+
upload_with_executor(
|
|
62
|
+
read_pipe,
|
|
63
|
+
completed_parts,
|
|
64
|
+
errors,
|
|
65
|
+
upload_part_opts(options).merge(upload_id: upload_id)
|
|
66
|
+
)
|
|
84
67
|
end
|
|
85
|
-
|
|
68
|
+
|
|
69
|
+
block.call(write_pipe)
|
|
70
|
+
ensure
|
|
71
|
+
# Ensure the pipe is closed to avoid https://github.com/jruby/jruby/issues/6111
|
|
72
|
+
write_pipe.close
|
|
73
|
+
upload_thread.join
|
|
86
74
|
end
|
|
87
|
-
rescue => e
|
|
88
|
-
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
errors << e
|
|
89
77
|
end
|
|
78
|
+
return ordered_parts(completed_parts) if errors.empty?
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
Array.new(completed.size) { completed.pop }.sort_by { |part| part[:part_number] }
|
|
93
|
-
else
|
|
94
|
-
abort_upload(upload_id, options, errors)
|
|
95
|
-
end
|
|
80
|
+
abort_upload(upload_id, options, errors)
|
|
96
81
|
end
|
|
97
82
|
|
|
98
83
|
def abort_upload(upload_id, options, errors)
|
|
99
|
-
@client.abort_multipart_upload(
|
|
100
|
-
|
|
101
|
-
key: options[:key],
|
|
102
|
-
upload_id: upload_id
|
|
103
|
-
)
|
|
104
|
-
msg = "multipart upload failed: #{errors.map(&:message).join("; ")}"
|
|
84
|
+
@client.abort_multipart_upload(bucket: options[:bucket], key: options[:key], upload_id: upload_id)
|
|
85
|
+
msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
105
86
|
raise MultipartUploadError.new(msg, errors)
|
|
106
|
-
rescue MultipartUploadError =>
|
|
107
|
-
raise
|
|
108
|
-
rescue =>
|
|
109
|
-
msg = "failed to abort multipart upload: #{
|
|
110
|
-
|
|
87
|
+
rescue MultipartUploadError => e
|
|
88
|
+
raise e
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
msg = "failed to abort multipart upload: #{e.message}. "\
|
|
91
|
+
"Multipart upload failed: #{errors.map(&:message).join('; ')}"
|
|
92
|
+
raise MultipartUploadError.new(msg, errors + [e])
|
|
111
93
|
end
|
|
112
94
|
|
|
113
95
|
def create_opts(options)
|
|
114
|
-
CREATE_OPTIONS.
|
|
96
|
+
CREATE_OPTIONS.each_with_object({}) do |key, hash|
|
|
115
97
|
hash[key] = options[key] if options.key?(key)
|
|
116
|
-
hash
|
|
117
98
|
end
|
|
118
99
|
end
|
|
119
100
|
|
|
120
101
|
def upload_part_opts(options)
|
|
121
|
-
UPLOAD_PART_OPTIONS.
|
|
102
|
+
UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
|
|
122
103
|
hash[key] = options[key] if options.key?(key)
|
|
123
|
-
hash
|
|
124
104
|
end
|
|
125
105
|
end
|
|
126
106
|
|
|
127
107
|
def complete_opts(options)
|
|
128
|
-
COMPLETE_UPLOAD_OPTIONS.
|
|
108
|
+
COMPLETE_UPLOAD_OPTIONS.each_with_object({}) do |key, hash|
|
|
129
109
|
hash[key] = options[key] if options.key?(key)
|
|
130
|
-
hash
|
|
131
110
|
end
|
|
132
111
|
end
|
|
133
112
|
|
|
134
113
|
def read_to_part_body(read_pipe)
|
|
135
114
|
return if read_pipe.closed?
|
|
136
|
-
|
|
115
|
+
|
|
116
|
+
temp_io = @tempfile ? Tempfile.new('aws-sdk-s3-upload_stream') : StringIO.new(String.new)
|
|
137
117
|
temp_io.binmode
|
|
138
118
|
bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
|
|
139
119
|
temp_io.rewind
|
|
140
|
-
if bytes_copied
|
|
141
|
-
if Tempfile
|
|
120
|
+
if bytes_copied.zero?
|
|
121
|
+
if temp_io.is_a?(Tempfile)
|
|
142
122
|
temp_io.close
|
|
143
123
|
temp_io.unlink
|
|
144
124
|
end
|
|
@@ -148,51 +128,62 @@ module Aws
|
|
|
148
128
|
end
|
|
149
129
|
end
|
|
150
130
|
|
|
151
|
-
def
|
|
152
|
-
|
|
131
|
+
def upload_with_executor(read_pipe, completed, errors, options)
|
|
132
|
+
completion_queue = Queue.new
|
|
133
|
+
queued_parts = 0
|
|
153
134
|
part_number = 0
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if part[:checksum_algorithm]
|
|
172
|
-
k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
|
|
173
|
-
completed_part[k] = resp[k]
|
|
174
|
-
end
|
|
175
|
-
completed.push(completed_part)
|
|
176
|
-
ensure
|
|
177
|
-
if Tempfile === body
|
|
178
|
-
body.close
|
|
179
|
-
body.unlink
|
|
180
|
-
elsif StringIO === body
|
|
181
|
-
body.string.clear
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
nil
|
|
186
|
-
rescue => error
|
|
187
|
-
# keep other threads from uploading other parts
|
|
188
|
-
mutex.synchronize do
|
|
189
|
-
thread_errors.push(error)
|
|
190
|
-
read_pipe.close_read unless read_pipe.closed?
|
|
191
|
-
end
|
|
192
|
-
error
|
|
135
|
+
mutex = Mutex.new
|
|
136
|
+
loop do
|
|
137
|
+
part_body, current_part_num = mutex.synchronize do
|
|
138
|
+
[read_to_part_body(read_pipe), part_number += 1]
|
|
139
|
+
end
|
|
140
|
+
break unless part_body || current_part_num == 1
|
|
141
|
+
|
|
142
|
+
queued_parts += 1
|
|
143
|
+
@executor.post(part_body, current_part_num, options) do |body, num, opts|
|
|
144
|
+
part = opts.merge(body: body, part_number: num)
|
|
145
|
+
resp = @client.upload_part(part)
|
|
146
|
+
completed_part = create_completed_part(resp, part)
|
|
147
|
+
completed.push(completed_part)
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
mutex.synchronize do
|
|
150
|
+
errors.push(e)
|
|
151
|
+
read_pipe.close_read unless read_pipe.closed?
|
|
193
152
|
end
|
|
153
|
+
ensure
|
|
154
|
+
clear_body(body)
|
|
155
|
+
completion_queue << :done
|
|
194
156
|
end
|
|
195
|
-
|
|
157
|
+
end
|
|
158
|
+
queued_parts.times { completion_queue.pop }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def create_completed_part(resp, part)
|
|
162
|
+
completed_part = { etag: resp.etag, part_number: part[:part_number] }
|
|
163
|
+
return completed_part unless part[:checksum_algorithm]
|
|
164
|
+
|
|
165
|
+
# get the requested checksum from the response
|
|
166
|
+
k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
|
|
167
|
+
completed_part[k] = resp[k]
|
|
168
|
+
completed_part
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def ordered_parts(parts)
|
|
172
|
+
sorted = []
|
|
173
|
+
until parts.empty?
|
|
174
|
+
part = parts.pop
|
|
175
|
+
index = sorted.bsearch_index { |p| p[:part_number] >= part[:part_number] } || sorted.size
|
|
176
|
+
sorted.insert(index, part)
|
|
177
|
+
end
|
|
178
|
+
sorted
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def clear_body(body)
|
|
182
|
+
if body.is_a?(Tempfile)
|
|
183
|
+
body.close
|
|
184
|
+
body.unlink
|
|
185
|
+
elsif body.is_a?(StringIO)
|
|
186
|
+
body.string.clear
|
|
196
187
|
end
|
|
197
188
|
end
|
|
198
189
|
end
|