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
@@ -9,25 +9,11 @@ module Aws
9
9
  class MultipartFileUploader
10
10
 
11
11
  MIN_PART_SIZE = 5 * 1024 * 1024 # 5MB
12
-
13
- FILE_TOO_SMALL = "unable to multipart upload files smaller than 5MB"
14
-
15
12
  MAX_PARTS = 10_000
16
-
17
- THREAD_COUNT = 10
18
-
19
- CREATE_OPTIONS = Set.new(
20
- Client.api.operation(:create_multipart_upload).input.shape.member_names
21
- )
22
-
23
- COMPLETE_OPTIONS = Set.new(
24
- Client.api.operation(:complete_multipart_upload).input.shape.member_names
25
- )
26
-
27
- UPLOAD_PART_OPTIONS = Set.new(
28
- Client.api.operation(:upload_part).input.shape.member_names
29
- )
30
-
13
+ DEFAULT_THREAD_COUNT = 10
14
+ CREATE_OPTIONS = Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
15
+ COMPLETE_OPTIONS = Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
16
+ UPLOAD_PART_OPTIONS = Set.new(Client.api.operation(:upload_part).input.shape.member_names)
31
17
  CHECKSUM_KEYS = Set.new(
32
18
  Client.api.operation(:upload_part).input.shape.members.map do |n, s|
33
19
  n if s.location == 'header' && s.location_name.start_with?('x-amz-checksum-')
@@ -35,10 +21,10 @@ module Aws
35
21
  )
36
22
 
37
23
  # @option options [Client] :client
38
- # @option options [Integer] :thread_count (THREAD_COUNT)
24
+ # @option options [Integer] :thread_count (DEFAULT_THREAD_COUNT)
39
25
  def initialize(options = {})
40
26
  @client = options[:client] || Client.new
41
- @thread_count = options[:thread_count] || THREAD_COUNT
27
+ @thread_count = options[:thread_count] || DEFAULT_THREAD_COUNT
42
28
  end
43
29
 
44
30
  # @return [Client]
@@ -52,13 +38,11 @@ module Aws
52
38
  # It will be invoked with [bytes_read], [total_sizes]
53
39
  # @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
54
40
  def upload(source, options = {})
55
- if File.size(source) < MIN_PART_SIZE
56
- raise ArgumentError, FILE_TOO_SMALL
57
- else
58
- upload_id = initiate_upload(options)
59
- parts = upload_parts(upload_id, source, options)
60
- complete_upload(upload_id, parts, options)
61
- end
41
+ raise ArgumentError, 'unable to multipart upload files smaller than 5MB' if File.size(source) < MIN_PART_SIZE
42
+
43
+ upload_id = initiate_upload(options)
44
+ parts = upload_parts(upload_id, source, options)
45
+ complete_upload(upload_id, parts, source, options)
62
46
  end
63
47
 
64
48
  private
@@ -67,18 +51,21 @@ module Aws
67
51
  @client.create_multipart_upload(create_opts(options)).upload_id
68
52
  end
69
53
 
70
- def complete_upload(upload_id, parts, options)
54
+ def complete_upload(upload_id, parts, source, options)
71
55
  @client.complete_multipart_upload(
72
56
  **complete_opts(options).merge(
73
57
  upload_id: upload_id,
74
- multipart_upload: { parts: parts }
58
+ multipart_upload: { parts: parts },
59
+ mpu_object_size: File.size(source)
75
60
  )
76
61
  )
62
+ rescue StandardError => e
63
+ abort_upload(upload_id, options, [e])
77
64
  end
78
65
 
79
66
  def upload_parts(upload_id, source, options)
80
- pending = PartList.new(compute_parts(upload_id, source, options))
81
67
  completed = PartList.new
68
+ pending = PartList.new(compute_parts(upload_id, source, options))
82
69
  errors = upload_in_threads(pending, completed, options)
83
70
  if errors.empty?
84
71
  completed.to_a.sort_by { |part| part[:part_number] }
@@ -88,19 +75,15 @@ module Aws
88
75
  end
89
76
 
90
77
  def abort_upload(upload_id, options, errors)
91
- @client.abort_multipart_upload(
92
- bucket: options[:bucket],
93
- key: options[:key],
94
- upload_id: upload_id
95
- )
78
+ @client.abort_multipart_upload(bucket: options[:bucket], key: options[:key], upload_id: upload_id)
96
79
  msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
97
80
  raise MultipartUploadError.new(msg, errors)
98
- rescue MultipartUploadError => error
99
- raise error
100
- rescue => error
101
- msg = "failed to abort multipart upload: #{error.message}. "\
81
+ rescue MultipartUploadError => e
82
+ raise e
83
+ rescue StandardError => e
84
+ msg = "failed to abort multipart upload: #{e.message}. "\
102
85
  "Multipart upload failed: #{errors.map(&:message).join('; ')}"
103
- raise MultipartUploadError.new(msg, errors + [error])
86
+ raise MultipartUploadError.new(msg, errors + [e])
104
87
  end
105
88
 
106
89
  def compute_parts(upload_id, source, options)
@@ -113,11 +96,7 @@ module Aws
113
96
  parts << upload_part_opts(options).merge(
114
97
  upload_id: upload_id,
115
98
  part_number: part_number,
116
- body: FilePart.new(
117
- source: source,
118
- offset: offset,
119
- size: part_size(size, default_part_size, offset)
120
- )
99
+ body: FilePart.new(source: source, offset: offset, size: part_size(size, default_part_size, offset))
121
100
  )
122
101
  part_number += 1
123
102
  offset += default_part_size
@@ -136,28 +115,23 @@ module Aws
136
115
  def create_opts(options)
137
116
  opts = { checksum_algorithm: Aws::Plugins::ChecksumAlgorithm::DEFAULT_CHECKSUM }
138
117
  opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
139
- CREATE_OPTIONS.inject(opts) do |hash, key|
118
+ CREATE_OPTIONS.each_with_object(opts) do |key, hash|
140
119
  hash[key] = options[key] if options.key?(key)
141
- hash
142
120
  end
143
121
  end
144
122
 
145
123
  def complete_opts(options)
146
124
  opts = {}
147
125
  opts[:checksum_type] = 'FULL_OBJECT' if has_checksum_key?(options.keys)
148
- COMPLETE_OPTIONS.inject(opts) do |hash, key|
126
+ COMPLETE_OPTIONS.each_with_object(opts) do |key, hash|
149
127
  hash[key] = options[key] if options.key?(key)
150
- hash
151
128
  end
152
129
  end
153
130
 
154
131
  def upload_part_opts(options)
155
- UPLOAD_PART_OPTIONS.inject({}) do |hash, key|
156
- if options.key?(key)
157
- # don't pass through checksum calculations
158
- hash[key] = options[key] unless checksum_key?(key)
159
- end
160
- hash
132
+ UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
133
+ # don't pass through checksum calculations
134
+ hash[key] = options[key] if options.key?(key) && !checksum_key?(key)
161
135
  end
162
136
  end
163
137
 
@@ -169,7 +143,7 @@ module Aws
169
143
  options.fetch(:thread_count, @thread_count).times do
170
144
  thread = Thread.new do
171
145
  begin
172
- while part = pending.shift
146
+ while (part = pending.shift)
173
147
  if progress
174
148
  part[:on_chunk_sent] =
175
149
  proc do |_chunk, bytes, _total|
@@ -178,20 +152,17 @@ module Aws
178
152
  end
179
153
  resp = @client.upload_part(part)
180
154
  part[:body].close
181
- completed_part = {
182
- etag: resp.etag,
183
- part_number: part[:part_number]
184
- }
155
+ completed_part = { etag: resp.etag, part_number: part[:part_number] }
185
156
  algorithm = resp.context.params[:checksum_algorithm]
186
157
  k = "checksum_#{algorithm.downcase}".to_sym
187
158
  completed_part[k] = resp.send(k)
188
159
  completed.push(completed_part)
189
160
  end
190
161
  nil
191
- rescue => error
162
+ rescue StandardError => e
192
163
  # keep other threads from uploading other parts
193
164
  pending.clear!
194
- error
165
+ e
195
166
  end
196
167
  end
197
168
  threads << thread
@@ -213,7 +184,6 @@ module Aws
213
184
 
214
185
  # @api private
215
186
  class PartList
216
-
217
187
  def initialize(parts = [])
218
188
  @parts = parts
219
189
  @mutex = Mutex.new
@@ -242,7 +212,6 @@ module Aws
242
212
  def to_a
243
213
  @mutex.synchronize { @parts.dup }
244
214
  end
245
-
246
215
  end
247
216
 
248
217
  # @api private
@@ -261,4 +230,4 @@ module Aws
261
230
  end
262
231
  end
263
232
  end
264
- end
233
+ end
@@ -9,33 +9,19 @@ 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
- # api private
16
- THREAD_COUNT = 10
17
-
18
- # api private
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
+ DEFAULT_THREAD_COUNT = 10
15
+ CREATE_OPTIONS = Set.new(Client.api.operation(:create_multipart_upload).input.shape.member_names)
16
+ UPLOAD_PART_OPTIONS = Set.new(Client.api.operation(:upload_part).input.shape.member_names)
17
+ COMPLETE_UPLOAD_OPTIONS = Set.new(Client.api.operation(:complete_multipart_upload).input.shape.member_names)
32
18
 
33
19
  # @option options [Client] :client
34
20
  def initialize(options = {})
35
21
  @client = options[:client] || Client.new
36
22
  @tempfile = options[:tempfile]
37
- @part_size = options[:part_size] || PART_SIZE
38
- @thread_count = options[:thread_count] || THREAD_COUNT
23
+ @part_size = options[:part_size] || DEFAULT_PART_SIZE
24
+ @thread_count = options[:thread_count] || DEFAULT_THREAD_COUNT
39
25
  end
40
26
 
41
27
  # @return [Client]
@@ -43,7 +29,7 @@ module Aws
43
29
 
44
30
  # @option options [required,String] :bucket
45
31
  # @option options [required,String] :key
46
- # @option options [Integer] :thread_count (THREAD_COUNT)
32
+ # @option options [Integer] :thread_count (DEFAULT_THREAD_COUNT)
47
33
  # @return [Seahorse::Client::Response] - the CompleteMultipartUploadResponse
48
34
  def upload(options = {}, &block)
49
35
  Aws::Plugins::UserAgent.metric('S3_TRANSFER') do
@@ -61,11 +47,10 @@ module Aws
61
47
 
62
48
  def complete_upload(upload_id, parts, options)
63
49
  @client.complete_multipart_upload(
64
- **complete_opts(options).merge(
65
- upload_id: upload_id,
66
- multipart_upload: { parts: parts }
67
- )
50
+ **complete_opts(options).merge(upload_id: upload_id, multipart_upload: { parts: parts })
68
51
  )
52
+ rescue StandardError => e
53
+ abort_upload(upload_id, options, [e])
69
54
  end
70
55
 
71
56
  def upload_parts(upload_id, options, &block)
@@ -74,9 +59,11 @@ module Aws
74
59
  errors = begin
75
60
  IO.pipe do |read_pipe, write_pipe|
76
61
  threads = upload_in_threads(
77
- read_pipe, completed,
62
+ read_pipe,
63
+ completed,
78
64
  upload_part_opts(options).merge(upload_id: upload_id),
79
- thread_errors)
65
+ thread_errors
66
+ )
80
67
  begin
81
68
  block.call(write_pipe)
82
69
  ensure
@@ -85,62 +72,53 @@ module Aws
85
72
  end
86
73
  threads.map(&:value).compact
87
74
  end
88
- rescue => e
75
+ rescue StandardError => e
89
76
  thread_errors + [e]
90
77
  end
78
+ return ordered_parts(completed) if errors.empty?
91
79
 
92
- if errors.empty?
93
- Array.new(completed.size) { completed.pop }.sort_by { |part| part[:part_number] }
94
- else
95
- abort_upload(upload_id, options, errors)
96
- end
80
+ abort_upload(upload_id, options, errors)
97
81
  end
98
82
 
99
83
  def abort_upload(upload_id, options, errors)
100
- @client.abort_multipart_upload(
101
- bucket: options[:bucket],
102
- key: options[:key],
103
- upload_id: upload_id
104
- )
84
+ @client.abort_multipart_upload(bucket: options[:bucket], key: options[:key], upload_id: upload_id)
105
85
  msg = "multipart upload failed: #{errors.map(&:message).join('; ')}"
106
86
  raise MultipartUploadError.new(msg, errors)
107
- rescue MultipartUploadError => error
108
- raise error
109
- rescue => error
110
- msg = "failed to abort multipart upload: #{error.message}. "\
87
+ rescue MultipartUploadError => e
88
+ raise e
89
+ rescue StandardError => e
90
+ msg = "failed to abort multipart upload: #{e.message}. "\
111
91
  "Multipart upload failed: #{errors.map(&:message).join('; ')}"
112
- raise MultipartUploadError.new(msg, errors + [error])
92
+ raise MultipartUploadError.new(msg, errors + [e])
113
93
  end
114
94
 
115
95
  def create_opts(options)
116
- CREATE_OPTIONS.inject({}) do |hash, key|
96
+ CREATE_OPTIONS.each_with_object({}) do |key, hash|
117
97
  hash[key] = options[key] if options.key?(key)
118
- hash
119
98
  end
120
99
  end
121
100
 
122
101
  def upload_part_opts(options)
123
- UPLOAD_PART_OPTIONS.inject({}) do |hash, key|
102
+ UPLOAD_PART_OPTIONS.each_with_object({}) do |key, hash|
124
103
  hash[key] = options[key] if options.key?(key)
125
- hash
126
104
  end
127
105
  end
128
106
 
129
107
  def complete_opts(options)
130
- COMPLETE_UPLOAD_OPTIONS.inject({}) do |hash, key|
108
+ COMPLETE_UPLOAD_OPTIONS.each_with_object({}) do |key, hash|
131
109
  hash[key] = options[key] if options.key?(key)
132
- hash
133
110
  end
134
111
  end
135
112
 
136
113
  def read_to_part_body(read_pipe)
137
114
  return if read_pipe.closed?
138
- temp_io = @tempfile ? Tempfile.new(TEMPFILE_PREIX) : StringIO.new(String.new)
115
+
116
+ temp_io = @tempfile ? Tempfile.new('aws-sdk-s3-upload_stream') : StringIO.new(String.new)
139
117
  temp_io.binmode
140
118
  bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
141
119
  temp_io.rewind
142
- if bytes_copied == 0
143
- if Tempfile === temp_io
120
+ if bytes_copied.zero?
121
+ if temp_io.is_a?(Tempfile)
144
122
  temp_io.close
145
123
  temp_io.unlink
146
124
  end
@@ -155,48 +133,62 @@ module Aws
155
133
  part_number = 0
156
134
  options.fetch(:thread_count, @thread_count).times.map do
157
135
  thread = Thread.new do
158
- begin
159
- loop do
160
- body, thread_part_number = mutex.synchronize do
161
- [read_to_part_body(read_pipe), part_number += 1]
162
- end
163
- break unless (body || thread_part_number == 1)
164
- begin
165
- part = options.merge(
166
- body: body,
167
- part_number: thread_part_number,
168
- )
169
- resp = @client.upload_part(part)
170
- completed_part = {etag: resp.etag, part_number: part[:part_number]}
171
-
172
- # get the requested checksum from the response
173
- if part[:checksum_algorithm]
174
- k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
175
- completed_part[k] = resp[k]
176
- end
177
- completed.push(completed_part)
178
- ensure
179
- if Tempfile === body
180
- body.close
181
- body.unlink
182
- elsif StringIO === body
183
- body.string.clear
184
- end
185
- end
136
+ loop do
137
+ body, thread_part_number = mutex.synchronize do
138
+ [read_to_part_body(read_pipe), part_number += 1]
186
139
  end
187
- nil
188
- rescue => error
189
- # keep other threads from uploading other parts
190
- mutex.synchronize do
191
- thread_errors.push(error)
192
- read_pipe.close_read unless read_pipe.closed?
140
+ break unless body || thread_part_number == 1
141
+
142
+ begin
143
+ part = options.merge(body: body, part_number: thread_part_number)
144
+ resp = @client.upload_part(part)
145
+ completed_part = create_completed_part(resp, part)
146
+ completed.push(completed_part)
147
+ ensure
148
+ clear_body(body)
193
149
  end
194
- error
195
150
  end
151
+ nil
152
+ rescue StandardError => e
153
+ # keep other threads from uploading other parts
154
+ mutex.synchronize do
155
+ thread_errors.push(e)
156
+ read_pipe.close_read unless read_pipe.closed?
157
+ end
158
+ e
196
159
  end
197
160
  thread
198
161
  end
199
162
  end
163
+
164
+ def create_completed_part(resp, part)
165
+ completed_part = { etag: resp.etag, part_number: part[:part_number] }
166
+ return completed_part unless part[:checksum_algorithm]
167
+
168
+ # get the requested checksum from the response
169
+ k = "checksum_#{part[:checksum_algorithm].downcase}".to_sym
170
+ completed_part[k] = resp[k]
171
+ completed_part
172
+ end
173
+
174
+ def ordered_parts(parts)
175
+ sorted = []
176
+ until parts.empty?
177
+ part = parts.pop
178
+ index = sorted.bsearch_index { |p| p[:part_number] >= part[:part_number] } || sorted.size
179
+ sorted.insert(index, part)
180
+ end
181
+ sorted
182
+ end
183
+
184
+ def clear_body(body)
185
+ if body.is_a?(Tempfile)
186
+ body.close
187
+ body.unlink
188
+ elsif body.is_a?(StringIO)
189
+ body.string.clear
190
+ end
191
+ end
200
192
  end
201
193
  end
202
194
  end
@@ -70,8 +70,10 @@ module Aws::S3
70
70
 
71
71
  # The class of storage used to store the object.
72
72
  #
73
- # <note markdown="1"> **Directory buckets** - Only the S3 Express One Zone storage class is
74
- # supported by directory buckets to store objects.
73
+ # <note markdown="1"> **Directory buckets** - Directory buckets only support
74
+ # `EXPRESS_ONEZONE` (the S3 Express One Zone storage class) in
75
+ # Availability Zones and `ONEZONE_IA` (the S3 One Zone-Infrequent Access
76
+ # storage class) in Dedicated Local Zones.
75
77
  #
76
78
  # </note>
77
79
  # @return [String]
@@ -2,17 +2,16 @@
2
2
 
3
3
  module Aws
4
4
  module S3
5
+ # Raise when multipart upload fails to complete.
5
6
  class MultipartUploadError < StandardError
6
7
 
7
- def initialize(message, errors)
8
+ def initialize(message, errors = [])
8
9
  @errors = errors
9
10
  super(message)
10
11
  end
11
12
 
12
- # @return [Array<StandardError>] The list of errors encountered
13
- # when uploading or aborting the upload.
13
+ # @return [Array<StandardError>] The list of errors encountered when uploading or aborting the upload.
14
14
  attr_reader :errors
15
-
16
15
  end
17
16
  end
18
17
  end