aws-sdk-s3 1.113.0 → 1.133.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +163 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-s3/bucket.rb +108 -55
  5. data/lib/aws-sdk-s3/bucket_acl.rb +9 -3
  6. data/lib/aws-sdk-s3/bucket_cors.rb +12 -4
  7. data/lib/aws-sdk-s3/bucket_lifecycle.rb +12 -4
  8. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +12 -4
  9. data/lib/aws-sdk-s3/bucket_logging.rb +9 -3
  10. data/lib/aws-sdk-s3/bucket_notification.rb +9 -3
  11. data/lib/aws-sdk-s3/bucket_policy.rb +12 -4
  12. data/lib/aws-sdk-s3/bucket_request_payment.rb +9 -3
  13. data/lib/aws-sdk-s3/bucket_tagging.rb +12 -4
  14. data/lib/aws-sdk-s3/bucket_versioning.rb +15 -5
  15. data/lib/aws-sdk-s3/bucket_website.rb +12 -4
  16. data/lib/aws-sdk-s3/client.rb +1894 -1573
  17. data/lib/aws-sdk-s3/client_api.rb +213 -189
  18. data/lib/aws-sdk-s3/customizations/bucket.rb +23 -47
  19. data/lib/aws-sdk-s3/customizations/errors.rb +27 -0
  20. data/lib/aws-sdk-s3/customizations/object.rb +95 -19
  21. data/lib/aws-sdk-s3/customizations/types/permanent_redirect.rb +26 -0
  22. data/lib/aws-sdk-s3/customizations.rb +2 -0
  23. data/lib/aws-sdk-s3/encryption/client.rb +6 -2
  24. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +13 -9
  25. data/lib/aws-sdk-s3/encryptionV2/client.rb +6 -2
  26. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +1 -0
  27. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +10 -6
  28. data/lib/aws-sdk-s3/endpoint_parameters.rb +146 -0
  29. data/lib/aws-sdk-s3/endpoint_provider.rb +509 -0
  30. data/lib/aws-sdk-s3/endpoints.rb +2150 -0
  31. data/lib/aws-sdk-s3/file_downloader.rb +170 -44
  32. data/lib/aws-sdk-s3/file_uploader.rb +8 -6
  33. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +41 -13
  34. data/lib/aws-sdk-s3/multipart_upload.rb +12 -4
  35. data/lib/aws-sdk-s3/multipart_upload_part.rb +10 -4
  36. data/lib/aws-sdk-s3/object.rb +105 -77
  37. data/lib/aws-sdk-s3/object_acl.rb +9 -3
  38. data/lib/aws-sdk-s3/object_copier.rb +7 -5
  39. data/lib/aws-sdk-s3/object_multipart_copier.rb +41 -19
  40. data/lib/aws-sdk-s3/object_summary.rb +106 -65
  41. data/lib/aws-sdk-s3/object_version.rb +35 -9
  42. data/lib/aws-sdk-s3/plugins/accelerate.rb +3 -50
  43. data/lib/aws-sdk-s3/plugins/arn.rb +0 -184
  44. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +3 -39
  45. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +1 -6
  46. data/lib/aws-sdk-s3/plugins/dualstack.rb +1 -49
  47. data/lib/aws-sdk-s3/plugins/endpoints.rb +262 -0
  48. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +2 -1
  49. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +0 -29
  50. data/lib/aws-sdk-s3/plugins/s3_signer.rb +35 -124
  51. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +23 -2
  52. data/lib/aws-sdk-s3/presigned_post.rb +61 -59
  53. data/lib/aws-sdk-s3/presigner.rb +24 -35
  54. data/lib/aws-sdk-s3/resource.rb +7 -3
  55. data/lib/aws-sdk-s3/types.rb +714 -4040
  56. data/lib/aws-sdk-s3.rb +5 -1
  57. metadata +12 -11
  58. data/lib/aws-sdk-s3/arn/access_point_arn.rb +0 -69
  59. data/lib/aws-sdk-s3/arn/multi_region_access_point_arn.rb +0 -68
  60. data/lib/aws-sdk-s3/arn/object_lambda_arn.rb +0 -69
  61. data/lib/aws-sdk-s3/arn/outpost_access_point_arn.rb +0 -74
  62. data/lib/aws-sdk-s3/plugins/object_lambda_endpoint.rb +0 -25
@@ -32,39 +32,68 @@ module Aws
32
32
  }
33
33
  @params[:version_id] = options[:version_id] if options[:version_id]
34
34
 
35
- case @mode
36
- when 'auto' then multipart_download
37
- when 'single_request' then single_request
38
- when 'get_range'
39
- if @chunk_size
40
- resp = @client.head_object(@params)
41
- multithreaded_get_by_ranges(construct_chunks(resp.content_length))
35
+ # checksum_mode only supports the value "ENABLED"
36
+ # falsey values (false/nil) or "DISABLED" should be considered
37
+ # disabled and the api parameter should be unset.
38
+ if (checksum_mode = options.fetch(:checksum_mode, 'ENABLED'))
39
+ @params[:checksum_mode] = checksum_mode unless checksum_mode.upcase == 'DISABLED'
40
+ end
41
+ @on_checksum_validated = options[:on_checksum_validated]
42
+
43
+ @progress_callback = options[:progress_callback]
44
+
45
+ validate!
46
+
47
+ Aws::Plugins::UserAgent.feature('s3-transfer') do
48
+ case @mode
49
+ when 'auto' then multipart_download
50
+ when 'single_request' then single_request
51
+ when 'get_range'
52
+ if @chunk_size
53
+ resp = @client.head_object(@params)
54
+ multithreaded_get_by_ranges(resp.content_length)
55
+ else
56
+ msg = 'In :get_range mode, :chunk_size must be provided'
57
+ raise ArgumentError, msg
58
+ end
42
59
  else
43
- msg = 'In :get_range mode, :chunk_size must be provided'
60
+ msg = "Invalid mode #{@mode} provided, "\
61
+ 'mode should be :single_request, :get_range or :auto'
44
62
  raise ArgumentError, msg
45
63
  end
46
- else
47
- msg = "Invalid mode #{@mode} provided, "\
48
- 'mode should be :single_request, :get_range or :auto'
49
- raise ArgumentError, msg
50
64
  end
51
65
  end
52
66
 
53
67
  private
54
68
 
69
+ def validate!
70
+ if @on_checksum_validated && @params[:checksum_mode] != 'ENABLED'
71
+ raise ArgumentError, "You must set checksum_mode: 'ENABLED' " +
72
+ "when providing a on_checksum_validated callback"
73
+ end
74
+
75
+ if @on_checksum_validated && !@on_checksum_validated.respond_to?(:call)
76
+ raise ArgumentError, 'on_checksum_validated must be callable'
77
+ end
78
+ end
79
+
55
80
  def multipart_download
56
81
  resp = @client.head_object(@params.merge(part_number: 1))
57
82
  count = resp.parts_count
58
83
  if count.nil? || count <= 1
59
- resp.content_length < MIN_CHUNK_SIZE ?
60
- single_request :
61
- multithreaded_get_by_ranges(construct_chunks(resp.content_length))
84
+ if resp.content_length <= MIN_CHUNK_SIZE
85
+ single_request
86
+ else
87
+ multithreaded_get_by_ranges(resp.content_length)
88
+ end
62
89
  else
63
90
  # partNumber is an option
64
91
  resp = @client.head_object(@params)
65
- resp.content_length < MIN_CHUNK_SIZE ?
66
- single_request :
92
+ if resp.content_length <= MIN_CHUNK_SIZE
93
+ single_request
94
+ else
67
95
  compute_mode(resp.content_length, count)
96
+ end
68
97
  end
69
98
  end
70
99
 
@@ -72,9 +101,9 @@ module Aws
72
101
  chunk_size = compute_chunk(file_size)
73
102
  part_size = (file_size.to_f / count.to_f).ceil
74
103
  if chunk_size < part_size
75
- multithreaded_get_by_ranges(construct_chunks(file_size))
104
+ multithreaded_get_by_ranges(file_size)
76
105
  else
77
- multithreaded_get_by_parts(count)
106
+ multithreaded_get_by_parts(count, file_size)
78
107
  end
79
108
  end
80
109
 
@@ -82,10 +111,11 @@ module Aws
82
111
  offset = 0
83
112
  default_chunk_size = compute_chunk(file_size)
84
113
  chunks = []
85
- while offset <= file_size
114
+ while offset < file_size
86
115
  progress = offset + default_chunk_size
87
- chunks << "bytes=#{offset}-#{progress < file_size ? progress : file_size}"
88
- offset = progress + 1
116
+ progress = file_size if progress > file_size
117
+ chunks << "bytes=#{offset}-#{progress - 1}"
118
+ offset = progress
89
119
  end
90
120
  chunks
91
121
  end
@@ -94,12 +124,9 @@ module Aws
94
124
  if @chunk_size && @chunk_size > file_size
95
125
  raise ArgumentError, ":chunk_size shouldn't exceed total file size."
96
126
  else
97
- chunk_size = @chunk_size || [
98
- (file_size.to_f / MAX_PARTS).ceil,
99
- MIN_CHUNK_SIZE
127
+ @chunk_size || [
128
+ (file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE
100
129
  ].max.to_i
101
- chunk_size -= 1 if file_size % chunk_size == 1
102
- chunk_size
103
130
  end
104
131
  end
105
132
 
@@ -108,27 +135,65 @@ module Aws
108
135
  chunks.each_slice(@thread_count).to_a
109
136
  end
110
137
 
111
- def multithreaded_get_by_ranges(chunks)
112
- thread_batches(chunks, 'range')
138
+ def multithreaded_get_by_ranges(file_size)
139
+ offset = 0
140
+ default_chunk_size = compute_chunk(file_size)
141
+ chunks = []
142
+ part_number = 1 # parts start at 1
143
+ while offset < file_size
144
+ progress = offset + default_chunk_size
145
+ progress = file_size if progress > file_size
146
+ range = "bytes=#{offset}-#{progress - 1}"
147
+ chunks << Part.new(
148
+ part_number: part_number,
149
+ size: (progress-offset),
150
+ params: @params.merge(range: range)
151
+ )
152
+ part_number += 1
153
+ offset = progress
154
+ end
155
+ download_in_threads(PartList.new(chunks), file_size)
113
156
  end
114
157
 
115
- def multithreaded_get_by_parts(parts)
116
- thread_batches(parts, 'part_number')
158
+ def multithreaded_get_by_parts(n_parts, total_size)
159
+ parts = (1..n_parts).map do |part|
160
+ Part.new(part_number: part, params: @params.merge(part_number: part))
161
+ end
162
+ download_in_threads(PartList.new(parts), total_size)
117
163
  end
118
164
 
119
- def thread_batches(chunks, param)
120
- batches(chunks, param).each do |batch|
121
- threads = []
122
- batch.each do |chunk|
123
- threads << Thread.new do
124
- resp = @client.get_object(
125
- @params.merge(param.to_sym => chunk)
126
- )
127
- write(resp)
165
+ def download_in_threads(pending, total_size)
166
+ threads = []
167
+ if @progress_callback
168
+ progress = MultipartProgress.new(pending, total_size, @progress_callback)
169
+ end
170
+ @thread_count.times do
171
+ thread = Thread.new do
172
+ begin
173
+ while part = pending.shift
174
+ if progress
175
+ part.params[:on_chunk_received] =
176
+ proc do |_chunk, bytes, total|
177
+ progress.call(part.part_number, bytes, total)
178
+ end
179
+ end
180
+ resp = @client.get_object(part.params)
181
+ write(resp)
182
+ if @on_checksum_validated && resp.checksum_validated
183
+ @on_checksum_validated.call(resp.checksum_validated, resp)
184
+ end
185
+ end
186
+ nil
187
+ rescue => error
188
+ # keep other threads from downloading other parts
189
+ pending.clear!
190
+ raise error
128
191
  end
129
192
  end
130
- threads.each(&:join)
193
+ thread.abort_on_exception = true
194
+ threads << thread
131
195
  end
196
+ threads.map(&:value).compact
132
197
  end
133
198
 
134
199
  def write(resp)
@@ -138,9 +203,70 @@ module Aws
138
203
  end
139
204
 
140
205
  def single_request
141
- @client.get_object(
142
- @params.merge(response_target: @path)
143
- )
206
+ params = @params.merge(response_target: @path)
207
+ params[:on_chunk_received] = single_part_progress if @progress_callback
208
+ resp = @client.get_object(params)
209
+
210
+ return resp unless @on_checksum_validated
211
+
212
+ if resp.checksum_validated
213
+ @on_checksum_validated.call(resp.checksum_validated, resp)
214
+ end
215
+
216
+ resp
217
+ end
218
+
219
+ def single_part_progress
220
+ proc do |_chunk, bytes_read, total_size|
221
+ @progress_callback.call([bytes_read], [total_size], total_size)
222
+ end
223
+ end
224
+
225
+ class Part < Struct.new(:part_number, :size, :params)
226
+ include Aws::Structure
227
+ end
228
+
229
+ # @api private
230
+ class PartList
231
+ include Enumerable
232
+ def initialize(parts = [])
233
+ @parts = parts
234
+ @mutex = Mutex.new
235
+ end
236
+
237
+ def shift
238
+ @mutex.synchronize { @parts.shift }
239
+ end
240
+
241
+ def size
242
+ @mutex.synchronize { @parts.size }
243
+ end
244
+
245
+ def clear!
246
+ @mutex.synchronize { @parts.clear }
247
+ end
248
+
249
+ def each(&block)
250
+ @mutex.synchronize { @parts.each(&block) }
251
+ end
252
+ end
253
+
254
+ # @api private
255
+ class MultipartProgress
256
+ def initialize(parts, total_size, progress_callback)
257
+ @bytes_received = Array.new(parts.size, 0)
258
+ @part_sizes = parts.map(&:size)
259
+ @total_size = total_size
260
+ @progress_callback = progress_callback
261
+ end
262
+
263
+ def call(part_number, bytes_received, total)
264
+ # part numbers start at 1
265
+ @bytes_received[part_number - 1] = bytes_received
266
+ # part size may not be known until we get the first response
267
+ @part_sizes[part_number - 1] ||= total
268
+ @progress_callback.call(@bytes_received, @part_sizes, @total_size)
269
+ end
144
270
  end
145
271
  end
146
272
  end
@@ -37,12 +37,14 @@ module Aws
37
37
  # objects smaller than the multipart threshold.
38
38
  # @return [void]
39
39
  def upload(source, options = {})
40
- if File.size(source) >= multipart_threshold
41
- MultipartFileUploader.new(@options).upload(source, options)
42
- else
43
- # remove multipart parameters not supported by put_object
44
- options.delete(:thread_count)
45
- put_object(source, options)
40
+ Aws::Plugins::UserAgent.feature('s3-transfer') do
41
+ if File.size(source) >= multipart_threshold
42
+ MultipartFileUploader.new(@options).upload(source, options)
43
+ else
44
+ # remove multipart parameters not supported by put_object
45
+ options.delete(:thread_count)
46
+ put_object(source, options)
47
+ end
46
48
  end
47
49
  end
48
50
 
@@ -26,6 +26,10 @@ module Aws
26
26
  UPLOAD_PART_OPTIONS =
27
27
  Set.new(Client.api.operation(:upload_part).input.shape.member_names)
28
28
 
29
+ # @api private
30
+ COMPLETE_UPLOAD_OPTIONS =
31
+ Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
32
+
29
33
  # @option options [Client] :client
30
34
  def initialize(options = {})
31
35
  @client = options[:client] || Client.new
@@ -39,11 +43,13 @@ module Aws
39
43
 
40
44
  # @option options [required,String] :bucket
41
45
  # @option options [required,String] :key
42
- # @return [void]
46
+ # @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
43
47
  def upload(options = {}, &block)
44
- upload_id = initiate_upload(options)
45
- parts = upload_parts(upload_id, options, &block)
46
- complete_upload(upload_id, parts, options)
48
+ Aws::Plugins::UserAgent.feature('s3-transfer') do
49
+ upload_id = initiate_upload(options)
50
+ parts = upload_parts(upload_id, options, &block)
51
+ complete_upload(upload_id, parts, options)
52
+ end
47
53
  end
48
54
 
49
55
  private
@@ -54,17 +60,22 @@ module Aws
54
60
 
55
61
  def complete_upload(upload_id, parts, options)
56
62
  @client.complete_multipart_upload(
57
- bucket: options[:bucket],
58
- key: options[:key],
59
- upload_id: upload_id,
60
- multipart_upload: { parts: parts })
63
+ **complete_opts(options).merge(
64
+ upload_id: upload_id,
65
+ multipart_upload: { parts: parts }
66
+ )
67
+ )
61
68
  end
62
69
 
63
70
  def upload_parts(upload_id, options, &block)
64
71
  completed = Queue.new
72
+ thread_errors = []
65
73
  errors = begin
66
74
  IO.pipe do |read_pipe, write_pipe|
67
- threads = upload_in_threads(read_pipe, completed, upload_part_opts(options).merge(upload_id: upload_id))
75
+ threads = upload_in_threads(
76
+ read_pipe, completed,
77
+ upload_part_opts(options).merge(upload_id: upload_id),
78
+ thread_errors)
68
79
  begin
69
80
  block.call(write_pipe)
70
81
  ensure
@@ -74,7 +85,7 @@ module Aws
74
85
  threads.map(&:value).compact
75
86
  end
76
87
  rescue => e
77
- [e]
88
+ thread_errors + [e]
78
89
  end
79
90
 
80
91
  if errors.empty?
@@ -113,6 +124,13 @@ module Aws
113
124
  end
114
125
  end
115
126
 
127
+ def complete_opts(options)
128
+ COMPLETE_UPLOAD_OPTIONS.inject({}) do |hash, key|
129
+ hash[key] = options[key] if options.key?(key)
130
+ hash
131
+ end
132
+ end
133
+
116
134
  def read_to_part_body(read_pipe)
117
135
  return if read_pipe.closed?
118
136
  temp_io = @tempfile ? Tempfile.new(TEMPFILE_PREIX) : StringIO.new(String.new)
@@ -130,7 +148,7 @@ module Aws
130
148
  end
131
149
  end
132
150
 
133
- def upload_in_threads(read_pipe, completed, options)
151
+ def upload_in_threads(read_pipe, completed, options, thread_errors)
134
152
  mutex = Mutex.new
135
153
  part_number = 0
136
154
  @thread_count.times.map do
@@ -147,7 +165,14 @@ module Aws
147
165
  part_number: thread_part_number,
148
166
  )
149
167
  resp = @client.upload_part(part)
150
- completed << {etag: resp.etag, part_number: part[:part_number]}
168
+ completed_part = {etag: resp.etag, part_number: part[:part_number]}
169
+
170
+ # get the requested checksum from the response
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)
151
176
  ensure
152
177
  if Tempfile === body
153
178
  body.close
@@ -160,7 +185,10 @@ module Aws
160
185
  nil
161
186
  rescue => error
162
187
  # keep other threads from uploading other parts
163
- mutex.synchronize { read_pipe.close_read unless read_pipe.closed? }
188
+ mutex.synchronize do
189
+ thread_errors.push(error)
190
+ read_pipe.close_read unless read_pipe.closed?
191
+ end
164
192
  error
165
193
  end
166
194
  end
@@ -217,7 +217,9 @@ module Aws::S3
217
217
  :retry
218
218
  end
219
219
  end
220
- Aws::Waiters::Waiter.new(options).wait({})
220
+ Aws::Plugins::UserAgent.feature('resource') do
221
+ Aws::Waiters::Waiter.new(options).wait({})
222
+ end
221
223
  end
222
224
 
223
225
  # @!group Actions
@@ -250,7 +252,9 @@ module Aws::S3
250
252
  key: @object_key,
251
253
  upload_id: @id
252
254
  )
253
- resp = @client.abort_multipart_upload(options)
255
+ resp = Aws::Plugins::UserAgent.feature('resource') do
256
+ @client.abort_multipart_upload(options)
257
+ end
254
258
  resp.data
255
259
  end
256
260
 
@@ -370,7 +374,9 @@ module Aws::S3
370
374
  key: @object_key,
371
375
  upload_id: @id
372
376
  )
373
- @client.complete_multipart_upload(options)
377
+ Aws::Plugins::UserAgent.feature('resource') do
378
+ @client.complete_multipart_upload(options)
379
+ end
374
380
  Object.new(
375
381
  bucket_name: @bucket_name,
376
382
  key: @object_key,
@@ -460,7 +466,9 @@ module Aws::S3
460
466
  key: @object_key,
461
467
  upload_id: @id
462
468
  )
463
- resp = @client.list_parts(options)
469
+ resp = Aws::Plugins::UserAgent.feature('resource') do
470
+ @client.list_parts(options)
471
+ end
464
472
  resp.each_page do |page|
465
473
  batch = []
466
474
  page.data.parts.each do |p|
@@ -256,7 +256,9 @@ module Aws::S3
256
256
  :retry
257
257
  end
258
258
  end
259
- Aws::Waiters::Waiter.new(options).wait({})
259
+ Aws::Plugins::UserAgent.feature('resource') do
260
+ Aws::Waiters::Waiter.new(options).wait({})
261
+ end
260
262
  end
261
263
 
262
264
  # @!group Actions
@@ -284,7 +286,7 @@ module Aws::S3
284
286
  # @option options [required, String] :copy_source
285
287
  # Specifies the source object for the copy operation. You specify the
286
288
  # value in one of two formats, depending on whether you want to access
287
- # the source object through an [access point][1]\:
289
+ # the source object through an [access point][1]:
288
290
  #
289
291
  # * For objects not accessed through an access point, specify the name
290
292
  # of the source bucket and key of the source object, separated by a
@@ -395,7 +397,9 @@ module Aws::S3
395
397
  upload_id: @multipart_upload_id,
396
398
  part_number: @part_number
397
399
  )
398
- resp = @client.upload_part_copy(options)
400
+ resp = Aws::Plugins::UserAgent.feature('resource') do
401
+ @client.upload_part_copy(options)
402
+ end
399
403
  resp.data
400
404
  end
401
405
 
@@ -521,7 +525,9 @@ module Aws::S3
521
525
  upload_id: @multipart_upload_id,
522
526
  part_number: @part_number
523
527
  )
524
- resp = @client.upload_part(options)
528
+ resp = Aws::Plugins::UserAgent.feature('resource') do
529
+ @client.upload_part(options)
530
+ end
525
531
  resp.data
526
532
  end
527
533