aws-sdk-s3 1.182.0 → 1.198.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -0
  3. data/VERSION +1 -1
  4. data/lib/aws-sdk-s3/bucket.rb +46 -6
  5. data/lib/aws-sdk-s3/client.rb +1693 -591
  6. data/lib/aws-sdk-s3/client_api.rb +396 -162
  7. data/lib/aws-sdk-s3/customizations/object.rb +57 -76
  8. data/lib/aws-sdk-s3/customizations.rb +2 -1
  9. data/lib/aws-sdk-s3/endpoint_provider.rb +234 -129
  10. data/lib/aws-sdk-s3/endpoints.rb +84 -0
  11. data/lib/aws-sdk-s3/errors.rb +11 -0
  12. data/lib/aws-sdk-s3/file_downloader.rb +65 -82
  13. data/lib/aws-sdk-s3/file_uploader.rb +3 -5
  14. data/lib/aws-sdk-s3/legacy_signer.rb +2 -1
  15. data/lib/aws-sdk-s3/multipart_download_error.rb +8 -0
  16. data/lib/aws-sdk-s3/multipart_file_uploader.rb +34 -65
  17. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +80 -88
  18. data/lib/aws-sdk-s3/multipart_upload.rb +4 -2
  19. data/lib/aws-sdk-s3/multipart_upload_error.rb +3 -4
  20. data/lib/aws-sdk-s3/object.rb +85 -26
  21. data/lib/aws-sdk-s3/object_acl.rb +7 -1
  22. data/lib/aws-sdk-s3/object_multipart_copier.rb +2 -1
  23. data/lib/aws-sdk-s3/object_summary.rb +54 -24
  24. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +5 -7
  25. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -1
  26. data/lib/aws-sdk-s3/resource.rb +6 -0
  27. data/lib/aws-sdk-s3/transfer_manager.rb +246 -0
  28. data/lib/aws-sdk-s3/types.rb +1627 -295
  29. data/lib/aws-sdk-s3.rb +1 -1
  30. data/sig/bucket.rbs +12 -3
  31. data/sig/client.rbs +143 -31
  32. data/sig/errors.rbs +2 -0
  33. data/sig/multipart_upload.rbs +1 -1
  34. data/sig/object.rbs +13 -10
  35. data/sig/object_summary.rbs +9 -9
  36. data/sig/resource.rbs +8 -1
  37. data/sig/types.rbs +183 -29
  38. metadata +8 -9
@@ -63,6 +63,18 @@ module Aws::S3
63
63
  end
64
64
  end
65
65
 
66
+ class CreateBucketMetadataConfiguration
67
+ def self.build(context)
68
+ Aws::S3::EndpointParameters.create(
69
+ context.config,
70
+ bucket: context.params[:bucket],
71
+ use_dual_stack: context[:use_dualstack_endpoint],
72
+ accelerate: context[:use_accelerate_endpoint],
73
+ use_s3_express_control_endpoint: true,
74
+ )
75
+ end
76
+ end
77
+
66
78
  class CreateBucketMetadataTableConfiguration
67
79
  def self.build(context)
68
80
  Aws::S3::EndpointParameters.create(
@@ -183,6 +195,18 @@ module Aws::S3
183
195
  end
184
196
  end
185
197
 
198
+ class DeleteBucketMetadataConfiguration
199
+ def self.build(context)
200
+ Aws::S3::EndpointParameters.create(
201
+ context.config,
202
+ bucket: context.params[:bucket],
203
+ use_dual_stack: context[:use_dualstack_endpoint],
204
+ accelerate: context[:use_accelerate_endpoint],
205
+ use_s3_express_control_endpoint: true,
206
+ )
207
+ end
208
+ end
209
+
186
210
  class DeleteBucketMetadataTableConfiguration
187
211
  def self.build(context)
188
212
  Aws::S3::EndpointParameters.create(
@@ -445,6 +469,18 @@ module Aws::S3
445
469
  end
446
470
  end
447
471
 
472
+ class GetBucketMetadataConfiguration
473
+ def self.build(context)
474
+ Aws::S3::EndpointParameters.create(
475
+ context.config,
476
+ bucket: context.params[:bucket],
477
+ use_dual_stack: context[:use_dualstack_endpoint],
478
+ accelerate: context[:use_accelerate_endpoint],
479
+ use_s3_express_control_endpoint: true,
480
+ )
481
+ end
482
+ end
483
+
448
484
  class GetBucketMetadataTableConfiguration
449
485
  def self.build(context)
450
486
  Aws::S3::EndpointParameters.create(
@@ -1162,6 +1198,18 @@ module Aws::S3
1162
1198
  end
1163
1199
  end
1164
1200
 
1201
+ class RenameObject
1202
+ def self.build(context)
1203
+ Aws::S3::EndpointParameters.create(
1204
+ context.config,
1205
+ bucket: context.params[:bucket],
1206
+ use_dual_stack: context[:use_dualstack_endpoint],
1207
+ accelerate: context[:use_accelerate_endpoint],
1208
+ key: context.params[:key],
1209
+ )
1210
+ end
1211
+ end
1212
+
1165
1213
  class RestoreObject
1166
1214
  def self.build(context)
1167
1215
  Aws::S3::EndpointParameters.create(
@@ -1184,6 +1232,30 @@ module Aws::S3
1184
1232
  end
1185
1233
  end
1186
1234
 
1235
+ class UpdateBucketMetadataInventoryTableConfiguration
1236
+ def self.build(context)
1237
+ Aws::S3::EndpointParameters.create(
1238
+ context.config,
1239
+ bucket: context.params[:bucket],
1240
+ use_dual_stack: context[:use_dualstack_endpoint],
1241
+ accelerate: context[:use_accelerate_endpoint],
1242
+ use_s3_express_control_endpoint: true,
1243
+ )
1244
+ end
1245
+ end
1246
+
1247
+ class UpdateBucketMetadataJournalTableConfiguration
1248
+ def self.build(context)
1249
+ Aws::S3::EndpointParameters.create(
1250
+ context.config,
1251
+ bucket: context.params[:bucket],
1252
+ use_dual_stack: context[:use_dualstack_endpoint],
1253
+ accelerate: context[:use_accelerate_endpoint],
1254
+ use_s3_express_control_endpoint: true,
1255
+ )
1256
+ end
1257
+ end
1258
+
1187
1259
  class UploadPart
1188
1260
  def self.build(context)
1189
1261
  Aws::S3::EndpointParameters.create(
@@ -1230,6 +1302,8 @@ module Aws::S3
1230
1302
  CopyObject.build(context)
1231
1303
  when :create_bucket
1232
1304
  CreateBucket.build(context)
1305
+ when :create_bucket_metadata_configuration
1306
+ CreateBucketMetadataConfiguration.build(context)
1233
1307
  when :create_bucket_metadata_table_configuration
1234
1308
  CreateBucketMetadataTableConfiguration.build(context)
1235
1309
  when :create_multipart_upload
@@ -1250,6 +1324,8 @@ module Aws::S3
1250
1324
  DeleteBucketInventoryConfiguration.build(context)
1251
1325
  when :delete_bucket_lifecycle
1252
1326
  DeleteBucketLifecycle.build(context)
1327
+ when :delete_bucket_metadata_configuration
1328
+ DeleteBucketMetadataConfiguration.build(context)
1253
1329
  when :delete_bucket_metadata_table_configuration
1254
1330
  DeleteBucketMetadataTableConfiguration.build(context)
1255
1331
  when :delete_bucket_metrics_configuration
@@ -1294,6 +1370,8 @@ module Aws::S3
1294
1370
  GetBucketLocation.build(context)
1295
1371
  when :get_bucket_logging
1296
1372
  GetBucketLogging.build(context)
1373
+ when :get_bucket_metadata_configuration
1374
+ GetBucketMetadataConfiguration.build(context)
1297
1375
  when :get_bucket_metadata_table_configuration
1298
1376
  GetBucketMetadataTableConfiguration.build(context)
1299
1377
  when :get_bucket_metrics_configuration
@@ -1416,10 +1494,16 @@ module Aws::S3
1416
1494
  PutObjectTagging.build(context)
1417
1495
  when :put_public_access_block
1418
1496
  PutPublicAccessBlock.build(context)
1497
+ when :rename_object
1498
+ RenameObject.build(context)
1419
1499
  when :restore_object
1420
1500
  RestoreObject.build(context)
1421
1501
  when :select_object_content
1422
1502
  SelectObjectContent.build(context)
1503
+ when :update_bucket_metadata_inventory_table_configuration
1504
+ UpdateBucketMetadataInventoryTableConfiguration.build(context)
1505
+ when :update_bucket_metadata_journal_table_configuration
1506
+ UpdateBucketMetadataJournalTableConfiguration.build(context)
1423
1507
  when :upload_part
1424
1508
  UploadPart.build(context)
1425
1509
  when :upload_part_copy
@@ -30,6 +30,7 @@ module Aws::S3
30
30
  # * {BucketAlreadyExists}
31
31
  # * {BucketAlreadyOwnedByYou}
32
32
  # * {EncryptionTypeMismatch}
33
+ # * {IdempotencyParameterMismatch}
33
34
  # * {InvalidObjectState}
34
35
  # * {InvalidRequest}
35
36
  # * {InvalidWriteOffset}
@@ -76,6 +77,16 @@ module Aws::S3
76
77
  end
77
78
  end
78
79
 
80
+ class IdempotencyParameterMismatch < ServiceError
81
+
82
+ # @param [Seahorse::Client::RequestContext] context
83
+ # @param [String] message
84
+ # @param [Aws::S3::Types::IdempotencyParameterMismatch] data
85
+ def initialize(context, message, data = Aws::EmptyStructure.new)
86
+ super(context, message, data)
87
+ end
88
+ end
89
+
79
90
  class InvalidObjectState < ServiceError
80
91
 
81
92
  # @param [Seahorse::Client::RequestContext] context
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
- require 'thread'
4
+ require 'securerandom'
5
5
  require 'set'
6
- require 'tmpdir'
7
6
 
8
7
  module Aws
9
8
  module S3
@@ -12,7 +11,6 @@ module Aws
12
11
 
13
12
  MIN_CHUNK_SIZE = 5 * 1024 * 1024
14
13
  MAX_PARTS = 10_000
15
- THREAD_COUNT = 10
16
14
 
17
15
  def initialize(options = {})
18
16
  @client = options[:client] || Client.new
@@ -22,18 +20,18 @@ module Aws
22
20
  attr_reader :client
23
21
 
24
22
  def download(destination, options = {})
25
- @path = destination
26
- @mode = options[:mode] || 'auto'
27
- @thread_count = options[:thread_count] || THREAD_COUNT
28
- @chunk_size = options[:chunk_size]
29
- @params = {
30
- bucket: options[:bucket],
31
- key: options[:key]
32
- }
33
- @params[:version_id] = options[:version_id] if options[:version_id]
34
- @on_checksum_validated = options[:on_checksum_validated]
35
- @progress_callback = options[:progress_callback]
36
-
23
+ valid_types = [String, Pathname, File, Tempfile]
24
+ unless valid_types.include?(destination.class)
25
+ raise ArgumentError, "Invalid destination, expected #{valid_types.join(', ')} but got: #{destination.class}"
26
+ end
27
+
28
+ @destination = destination
29
+ @mode = options.delete(:mode) || 'auto'
30
+ @thread_count = options.delete(:thread_count) || 10
31
+ @chunk_size = options.delete(:chunk_size)
32
+ @on_checksum_validated = options.delete(:on_checksum_validated)
33
+ @progress_callback = options.delete(:progress_callback)
34
+ @params = options
37
35
  validate!
38
36
 
39
37
  Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
@@ -41,88 +39,65 @@ module Aws
41
39
  when 'auto' then multipart_download
42
40
  when 'single_request' then single_request
43
41
  when 'get_range'
44
- if @chunk_size
45
- resp = @client.head_object(@params)
46
- multithreaded_get_by_ranges(resp.content_length)
47
- else
48
- msg = 'In :get_range mode, :chunk_size must be provided'
49
- raise ArgumentError, msg
50
- end
42
+ raise ArgumentError, 'In get_range mode, :chunk_size must be provided' unless @chunk_size
43
+
44
+ resp = @client.head_object(@params)
45
+ multithreaded_get_by_ranges(resp.content_length, resp.etag)
51
46
  else
52
- msg = "Invalid mode #{@mode} provided, "\
53
- 'mode should be :single_request, :get_range or :auto'
54
- raise ArgumentError, msg
47
+ raise ArgumentError, "Invalid mode #{@mode} provided, :mode should be single_request, get_range or auto"
55
48
  end
56
49
  end
50
+ File.rename(@temp_path, @destination) if @temp_path
51
+ ensure
52
+ File.delete(@temp_path) if @temp_path && File.exist?(@temp_path)
57
53
  end
58
54
 
59
55
  private
60
56
 
61
57
  def validate!
62
- if @on_checksum_validated && !@on_checksum_validated.respond_to?(:call)
63
- raise ArgumentError, 'on_checksum_validated must be callable'
64
- end
58
+ return unless @on_checksum_validated && !@on_checksum_validated.respond_to?(:call)
59
+
60
+ raise ArgumentError, ':on_checksum_validated must be callable'
65
61
  end
66
62
 
67
63
  def multipart_download
68
64
  resp = @client.head_object(@params.merge(part_number: 1))
69
65
  count = resp.parts_count
66
+
70
67
  if count.nil? || count <= 1
71
68
  if resp.content_length <= MIN_CHUNK_SIZE
72
69
  single_request
73
70
  else
74
- multithreaded_get_by_ranges(resp.content_length)
71
+ multithreaded_get_by_ranges(resp.content_length, resp.etag)
75
72
  end
76
73
  else
77
- # partNumber is an option
78
- resp = @client.head_object(@params)
74
+ # covers cases when given object is not uploaded via UploadPart API
75
+ resp = @client.head_object(@params) # partNumber is an option
79
76
  if resp.content_length <= MIN_CHUNK_SIZE
80
77
  single_request
81
78
  else
82
- compute_mode(resp.content_length, count)
79
+ compute_mode(resp.content_length, count, resp.etag)
83
80
  end
84
81
  end
85
82
  end
86
83
 
87
- def compute_mode(file_size, count)
84
+ def compute_mode(file_size, count, etag)
88
85
  chunk_size = compute_chunk(file_size)
89
- part_size = (file_size.to_f / count.to_f).ceil
86
+ part_size = (file_size.to_f / count).ceil
90
87
  if chunk_size < part_size
91
- multithreaded_get_by_ranges(file_size)
88
+ multithreaded_get_by_ranges(file_size, etag)
92
89
  else
93
- multithreaded_get_by_parts(count, file_size)
94
- end
95
- end
96
-
97
- def construct_chunks(file_size)
98
- offset = 0
99
- default_chunk_size = compute_chunk(file_size)
100
- chunks = []
101
- while offset < file_size
102
- progress = offset + default_chunk_size
103
- progress = file_size if progress > file_size
104
- chunks << "bytes=#{offset}-#{progress - 1}"
105
- offset = progress
90
+ multithreaded_get_by_parts(count, file_size, etag)
106
91
  end
107
- chunks
108
92
  end
109
93
 
110
94
  def compute_chunk(file_size)
111
- if @chunk_size && @chunk_size > file_size
112
- raise ArgumentError, ":chunk_size shouldn't exceed total file size."
113
- else
114
- @chunk_size || [
115
- (file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE
116
- ].max.to_i
117
- end
118
- end
95
+ raise ArgumentError, ":chunk_size shouldn't exceed total file size." if @chunk_size && @chunk_size > file_size
119
96
 
120
- def batches(chunks, mode)
121
- chunks = (1..chunks) if mode.eql? 'part_number'
122
- chunks.each_slice(@thread_count).to_a
97
+ @chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
123
98
  end
124
99
 
125
- def multithreaded_get_by_ranges(file_size)
100
+ def multithreaded_get_by_ranges(file_size, etag)
126
101
  offset = 0
127
102
  default_chunk_size = compute_chunk(file_size)
128
103
  chunks = []
@@ -130,21 +105,17 @@ module Aws
130
105
  while offset < file_size
131
106
  progress = offset + default_chunk_size
132
107
  progress = file_size if progress > file_size
133
- range = "bytes=#{offset}-#{progress - 1}"
134
- chunks << Part.new(
135
- part_number: part_number,
136
- size: (progress-offset),
137
- params: @params.merge(range: range)
138
- )
108
+ params = @params.merge(range: "bytes=#{offset}-#{progress - 1}", if_match: etag)
109
+ chunks << Part.new(part_number: part_number, size: (progress - offset), params: params)
139
110
  part_number += 1
140
111
  offset = progress
141
112
  end
142
113
  download_in_threads(PartList.new(chunks), file_size)
143
114
  end
144
115
 
145
- def multithreaded_get_by_parts(n_parts, total_size)
116
+ def multithreaded_get_by_parts(n_parts, total_size, etag)
146
117
  parts = (1..n_parts).map do |part|
147
- Part.new(part_number: part, params: @params.merge(part_number: part))
118
+ Part.new(part_number: part, params: @params.merge(part_number: part, if_match: etag))
148
119
  end
149
120
  download_in_threads(PartList.new(parts), total_size)
150
121
  end
@@ -152,10 +123,13 @@ module Aws
152
123
  def download_in_threads(pending, total_size)
153
124
  threads = []
154
125
  progress = MultipartProgress.new(pending, total_size, @progress_callback) if @progress_callback
126
+ unless [File, Tempfile].include?(@destination.class)
127
+ @temp_path = "#{@destination}.s3tmp.#{SecureRandom.alphanumeric(8)}"
128
+ end
155
129
  @thread_count.times do
156
130
  thread = Thread.new do
157
131
  begin
158
- while part = pending.shift
132
+ while (part = pending.shift)
159
133
  if progress
160
134
  part.params[:on_chunk_received] =
161
135
  proc do |_chunk, bytes, total|
@@ -163,16 +137,17 @@ module Aws
163
137
  end
164
138
  end
165
139
  resp = @client.get_object(part.params)
166
- write(resp)
140
+ range = extract_range(resp.content_range)
141
+ validate_range(range, part.params[:range]) if part.params[:range]
142
+ write(resp.body, range)
167
143
  if @on_checksum_validated && resp.checksum_validated
168
144
  @on_checksum_validated.call(resp.checksum_validated, resp)
169
145
  end
170
146
  end
171
147
  nil
172
- rescue => error
173
- # keep other threads from downloading other parts
174
- pending.clear!
175
- raise error
148
+ rescue StandardError => e
149
+ pending.clear! # keep other threads from downloading other parts
150
+ raise e
176
151
  end
177
152
  end
178
153
  threads << thread
@@ -180,21 +155,28 @@ module Aws
180
155
  threads.map(&:value).compact
181
156
  end
182
157
 
183
- def write(resp)
184
- range, _ = resp.content_range.split(' ').last.split('/')
185
- head, _ = range.split('-').map {|s| s.to_i}
186
- File.write(@path, resp.body.read, head)
158
+ def extract_range(value)
159
+ value.match(%r{bytes (?<range>\d+-\d+)/\d+})[:range]
160
+ end
161
+
162
+ def validate_range(actual, expected)
163
+ return if actual == expected.match(/bytes=(?<range>\d+-\d+)/)[:range]
164
+
165
+ raise MultipartDownloadError, "multipart download failed: expected range of #{expected} but got #{actual}"
166
+ end
167
+
168
+ def write(body, range)
169
+ path = @temp_path || @destination
170
+ File.write(path, body.read, range.split('-').first.to_i)
187
171
  end
188
172
 
189
173
  def single_request
190
- params = @params.merge(response_target: @path)
174
+ params = @params.merge(response_target: @destination)
191
175
  params[:on_chunk_received] = single_part_progress if @progress_callback
192
176
  resp = @client.get_object(params)
193
-
194
177
  return resp unless @on_checksum_validated
195
178
 
196
179
  @on_checksum_validated.call(resp.checksum_validated, resp) if resp.checksum_validated
197
-
198
180
  resp
199
181
  end
200
182
 
@@ -204,6 +186,7 @@ module Aws
204
186
  end
205
187
  end
206
188
 
189
+ # @api private
207
190
  class Part < Struct.new(:part_number, :size, :params)
208
191
  include Aws::Structure
209
192
  end
@@ -7,7 +7,7 @@ module Aws
7
7
  # @api private
8
8
  class FileUploader
9
9
 
10
- ONE_HUNDRED_MEGABYTES = 100 * 1024 * 1024
10
+ DEFAULT_MULTIPART_THRESHOLD = 100 * 1024 * 1024
11
11
 
12
12
  # @param [Hash] options
13
13
  # @option options [Client] :client
@@ -15,15 +15,13 @@ module Aws
15
15
  def initialize(options = {})
16
16
  @options = options
17
17
  @client = options[:client] || Client.new
18
- @multipart_threshold = options[:multipart_threshold] ||
19
- ONE_HUNDRED_MEGABYTES
18
+ @multipart_threshold = options[:multipart_threshold] || DEFAULT_MULTIPART_THRESHOLD
20
19
  end
21
20
 
22
21
  # @return [Client]
23
22
  attr_reader :client
24
23
 
25
- # @return [Integer] Files larger than or equal to this in bytes are uploaded
26
- # using a {MultipartFileUploader}.
24
+ # @return [Integer] Files larger than or equal to this in bytes are uploaded using a {MultipartFileUploader}.
27
25
  attr_reader :multipart_threshold
28
26
 
29
27
  # @param [String, Pathname, File, Tempfile] source The file to upload.
@@ -3,7 +3,8 @@
3
3
  require 'set'
4
4
  require 'time'
5
5
  require 'openssl'
6
- require 'cgi'
6
+ require "cgi/escape"
7
+ require "cgi/util" if RUBY_VERSION < "3.5"
7
8
  require 'aws-sdk-core/query'
8
9
 
9
10
  module Aws
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aws
4
+ module S3
5
+ # Raised when multipart download fails to complete.
6
+ class MultipartDownloadError < StandardError; end
7
+ end
8
+ end