aws-sdk-s3 1.13.0 → 1.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76fdd52e31b770c550908db83818c5842b0e704c
4
- data.tar.gz: 596f4b30103399a4bfe7b8cf2cad093cfd8ac6e1
3
+ metadata.gz: ebf985aabc5276f0257be4b38c436b5261e133aa
4
+ data.tar.gz: eb740e2166050fd5b48daf78d95797c167fc5d5b
5
5
  SHA512:
6
- metadata.gz: 4a3f32fa2257bb3dd5bdfd94b329a54a047e1fa6b6ccb1425e21b7e86c9664dd1d62165d400a65dbc6c814fa0935c08f2f3e9d7535cf7576094e4b332c745405
7
- data.tar.gz: 7e3729ceec9fe3cd819926dd14ba0fdf675af8610773f5a0daf5766fbd5bcfb6242bd4b91e1910dbb04c17732004bb46d8bd1976f22181d4625a95a3d1bf5381
6
+ metadata.gz: 5de1c034a92b407852bd6b268d42c3ecb8593f0d1c27ea0056e6b83f09263f42a4e8bfdfa3df18f01f0435bf1206a6f779c7d34e3c23d36a3e21510345a0da1e
7
+ data.tar.gz: 4a6d12d6dd43abb6064fd45b8ce703c98f46ffa51d220cd2b67a7f35e4ac1bcf00c0876874359f623db56b2f6ad95ff355b8669e967830bdd912a23868a7268f
@@ -63,6 +63,6 @@ require_relative 'aws-sdk-s3/event_streams'
63
63
  # @service
64
64
  module Aws::S3
65
65
 
66
- GEM_VERSION = '1.13.0'
66
+ GEM_VERSION = '1.14.0'
67
67
 
68
68
  end
@@ -6371,7 +6371,7 @@ module Aws::S3
6371
6371
  params: params,
6372
6372
  config: config)
6373
6373
  context[:gem_name] = 'aws-sdk-s3'
6374
- context[:gem_version] = '1.13.0'
6374
+ context[:gem_version] = '1.14.0'
6375
6375
  Seahorse::Client::Request.new(handlers, context)
6376
6376
  end
6377
6377
 
@@ -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'
@@ -214,6 +214,54 @@ 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_file do |write_stream|
227
+ # 10.times { write_stream << 'hello' }
228
+ # end
229
+ #
230
+ # @option options [Integer] :thread_count
231
+ # The number of parallel multipart uploads
232
+ # Default `:thread_count` is `10`.
233
+ #
234
+ # @option options [Boolean] :tempfile
235
+ # Normally read data is stored in memory when building the parts in order to complete
236
+ # the underlying multipart upload. By passing `:tempfile => true` data read will be
237
+ # temporarily stored on disk reducing the memory footprint vastly.
238
+ # Default `:tempfile` is `false`.
239
+ #
240
+ # @option options [Integer] :part_size
241
+ # Define how big each part size but the last should be.
242
+ # Default `:part_size` is `5 * 1024 * 1024`.
243
+ #
244
+ # @raise [MultipartUploadError] If an object is being uploaded in
245
+ # parts, and the upload can not be completed, then the upload is
246
+ # aborted and this error is raised. The raised error has a `#errors`
247
+ # method that returns the failures that caused the upload to be
248
+ # aborted.
249
+ #
250
+ # @return [Boolean] Returns `true` when the object is uploaded
251
+ # without any errors.
252
+ #
253
+ def upload_stream(options = {}, &block)
254
+ uploading_options = options.dup
255
+ uploader = MultipartStreamUploader.new(
256
+ client: client,
257
+ thread_count: uploading_options.delete(:thread_count),
258
+ tempfile: uploading_options.delete(:tempfile),
259
+ part_size: uploading_options.delete(:part_size),
260
+ )
261
+ uploader.upload(uploading_options.merge(bucket: bucket_name, key: key), &block)
262
+ true
263
+ end
264
+
217
265
  # Uploads a file from disk to the current object in S3.
218
266
  #
219
267
  # # small files are uploaded in a single API call
@@ -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)
@@ -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
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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-sdk-s3
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.0
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amazon Web Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-22 00:00:00.000000000 Z
11
+ date: 2018-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-kms
@@ -109,6 +109,7 @@ files:
109
109
  - lib/aws-sdk-s3/file_uploader.rb
110
110
  - lib/aws-sdk-s3/legacy_signer.rb
111
111
  - lib/aws-sdk-s3/multipart_file_uploader.rb
112
+ - lib/aws-sdk-s3/multipart_stream_uploader.rb
112
113
  - lib/aws-sdk-s3/multipart_upload.rb
113
114
  - lib/aws-sdk-s3/multipart_upload_error.rb
114
115
  - lib/aws-sdk-s3/multipart_upload_part.rb