aws-sdk-s3 1.75.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 +7 -0
- data/lib/aws-sdk-s3.rb +73 -0
- data/lib/aws-sdk-s3/bucket.rb +861 -0
- data/lib/aws-sdk-s3/bucket_acl.rb +277 -0
- data/lib/aws-sdk-s3/bucket_cors.rb +262 -0
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +264 -0
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +283 -0
- data/lib/aws-sdk-s3/bucket_logging.rb +251 -0
- data/lib/aws-sdk-s3/bucket_notification.rb +293 -0
- data/lib/aws-sdk-s3/bucket_policy.rb +242 -0
- data/lib/aws-sdk-s3/bucket_region_cache.rb +81 -0
- data/lib/aws-sdk-s3/bucket_request_payment.rb +236 -0
- data/lib/aws-sdk-s3/bucket_tagging.rb +251 -0
- data/lib/aws-sdk-s3/bucket_versioning.rb +312 -0
- data/lib/aws-sdk-s3/bucket_website.rb +292 -0
- data/lib/aws-sdk-s3/client.rb +11818 -0
- data/lib/aws-sdk-s3/client_api.rb +3014 -0
- data/lib/aws-sdk-s3/customizations.rb +34 -0
- data/lib/aws-sdk-s3/customizations/bucket.rb +162 -0
- data/lib/aws-sdk-s3/customizations/multipart_upload.rb +44 -0
- data/lib/aws-sdk-s3/customizations/object.rb +389 -0
- data/lib/aws-sdk-s3/customizations/object_summary.rb +85 -0
- data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +13 -0
- data/lib/aws-sdk-s3/encryption.rb +21 -0
- data/lib/aws-sdk-s3/encryption/client.rb +375 -0
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +190 -0
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +65 -0
- data/lib/aws-sdk-s3/encryption/default_key_provider.rb +40 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +61 -0
- data/lib/aws-sdk-s3/encryption/errors.rb +15 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +58 -0
- data/lib/aws-sdk-s3/encryption/io_decrypter.rb +36 -0
- data/lib/aws-sdk-s3/encryption/io_encrypter.rb +71 -0
- data/lib/aws-sdk-s3/encryption/key_provider.rb +31 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +75 -0
- data/lib/aws-sdk-s3/encryption/materials.rb +60 -0
- data/lib/aws-sdk-s3/encryption/utils.rb +81 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +388 -0
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +198 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +103 -0
- data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +38 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +66 -0
- data/lib/aws-sdk-s3/encryptionV2/errors.rb +13 -0
- data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +56 -0
- data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +71 -0
- data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +29 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +99 -0
- data/lib/aws-sdk-s3/encryptionV2/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +116 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +20 -0
- data/lib/aws-sdk-s3/errors.rb +115 -0
- data/lib/aws-sdk-s3/event_streams.rb +69 -0
- data/lib/aws-sdk-s3/file_downloader.rb +142 -0
- data/lib/aws-sdk-s3/file_part.rb +78 -0
- data/lib/aws-sdk-s3/file_uploader.rb +70 -0
- data/lib/aws-sdk-s3/legacy_signer.rb +189 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +227 -0
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +173 -0
- data/lib/aws-sdk-s3/multipart_upload.rb +401 -0
- data/lib/aws-sdk-s3/multipart_upload_error.rb +18 -0
- data/lib/aws-sdk-s3/multipart_upload_part.rb +423 -0
- data/lib/aws-sdk-s3/object.rb +1422 -0
- data/lib/aws-sdk-s3/object_acl.rb +333 -0
- data/lib/aws-sdk-s3/object_copier.rb +101 -0
- data/lib/aws-sdk-s3/object_multipart_copier.rb +182 -0
- data/lib/aws-sdk-s3/object_summary.rb +1181 -0
- data/lib/aws-sdk-s3/object_version.rb +550 -0
- data/lib/aws-sdk-s3/plugins/accelerate.rb +87 -0
- data/lib/aws-sdk-s3/plugins/bucket_arn.rb +212 -0
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +91 -0
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +45 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +74 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +28 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +25 -0
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -0
- data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +62 -0
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +35 -0
- data/lib/aws-sdk-s3/plugins/md5s.rb +84 -0
- data/lib/aws-sdk-s3/plugins/redirects.rb +45 -0
- data/lib/aws-sdk-s3/plugins/s3_host_id.rb +30 -0
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +222 -0
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +70 -0
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +118 -0
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +97 -0
- data/lib/aws-sdk-s3/presigned_post.rb +686 -0
- data/lib/aws-sdk-s3/presigner.rb +253 -0
- data/lib/aws-sdk-s3/resource.rb +117 -0
- data/lib/aws-sdk-s3/types.rb +13154 -0
- data/lib/aws-sdk-s3/waiters.rb +243 -0
- metadata +184 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WARNING ABOUT GENERATED CODE
|
|
4
|
+
#
|
|
5
|
+
# This file is generated. See the contributing guide for more information:
|
|
6
|
+
# https://github.com/aws/aws-sdk-ruby/blob/master/CONTRIBUTING.md
|
|
7
|
+
#
|
|
8
|
+
# WARNING ABOUT GENERATED CODE
|
|
9
|
+
|
|
10
|
+
module Aws::S3
|
|
11
|
+
|
|
12
|
+
class ObjectAcl
|
|
13
|
+
|
|
14
|
+
extend Aws::Deprecations
|
|
15
|
+
|
|
16
|
+
# @overload def initialize(bucket_name, object_key, options = {})
|
|
17
|
+
# @param [String] bucket_name
|
|
18
|
+
# @param [String] object_key
|
|
19
|
+
# @option options [Client] :client
|
|
20
|
+
# @overload def initialize(options = {})
|
|
21
|
+
# @option options [required, String] :bucket_name
|
|
22
|
+
# @option options [required, String] :object_key
|
|
23
|
+
# @option options [Client] :client
|
|
24
|
+
def initialize(*args)
|
|
25
|
+
options = Hash === args.last ? args.pop.dup : {}
|
|
26
|
+
@bucket_name = extract_bucket_name(args, options)
|
|
27
|
+
@object_key = extract_object_key(args, options)
|
|
28
|
+
@data = options.delete(:data)
|
|
29
|
+
@client = options.delete(:client) || Client.new(options)
|
|
30
|
+
@waiter_block_warned = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @!group Read-Only Attributes
|
|
34
|
+
|
|
35
|
+
# @return [String]
|
|
36
|
+
def bucket_name
|
|
37
|
+
@bucket_name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [String]
|
|
41
|
+
def object_key
|
|
42
|
+
@object_key
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Container for the bucket owner's display name and ID.
|
|
46
|
+
# @return [Types::Owner]
|
|
47
|
+
def owner
|
|
48
|
+
data[:owner]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A list of grants.
|
|
52
|
+
# @return [Array<Types::Grant>]
|
|
53
|
+
def grants
|
|
54
|
+
data[:grants]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# If present, indicates that the requester was successfully charged for
|
|
58
|
+
# the request.
|
|
59
|
+
# @return [String]
|
|
60
|
+
def request_charged
|
|
61
|
+
data[:request_charged]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @!endgroup
|
|
65
|
+
|
|
66
|
+
# @return [Client]
|
|
67
|
+
def client
|
|
68
|
+
@client
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Loads, or reloads {#data} for the current {ObjectAcl}.
|
|
72
|
+
# Returns `self` making it possible to chain methods.
|
|
73
|
+
#
|
|
74
|
+
# object_acl.reload.data
|
|
75
|
+
#
|
|
76
|
+
# @return [self]
|
|
77
|
+
def load
|
|
78
|
+
resp = @client.get_object_acl(
|
|
79
|
+
bucket: @bucket_name,
|
|
80
|
+
key: @object_key
|
|
81
|
+
)
|
|
82
|
+
@data = resp.data
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
alias :reload :load
|
|
86
|
+
|
|
87
|
+
# @return [Types::GetObjectAclOutput]
|
|
88
|
+
# Returns the data for this {ObjectAcl}. Calls
|
|
89
|
+
# {Client#get_object_acl} if {#data_loaded?} is `false`.
|
|
90
|
+
def data
|
|
91
|
+
load unless @data
|
|
92
|
+
@data
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [Boolean]
|
|
96
|
+
# Returns `true` if this resource is loaded. Accessing attributes or
|
|
97
|
+
# {#data} on an unloaded resource will trigger a call to {#load}.
|
|
98
|
+
def data_loaded?
|
|
99
|
+
!!@data
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @deprecated Use [Aws::S3::Client] #wait_until instead
|
|
103
|
+
#
|
|
104
|
+
# Waiter polls an API operation until a resource enters a desired
|
|
105
|
+
# state.
|
|
106
|
+
#
|
|
107
|
+
# @note The waiting operation is performed on a copy. The original resource
|
|
108
|
+
# remains unchanged.
|
|
109
|
+
#
|
|
110
|
+
# ## Basic Usage
|
|
111
|
+
#
|
|
112
|
+
# Waiter will polls until it is successful, it fails by
|
|
113
|
+
# entering a terminal state, or until a maximum number of attempts
|
|
114
|
+
# are made.
|
|
115
|
+
#
|
|
116
|
+
# # polls in a loop until condition is true
|
|
117
|
+
# resource.wait_until(options) {|resource| condition}
|
|
118
|
+
#
|
|
119
|
+
# ## Example
|
|
120
|
+
#
|
|
121
|
+
# instance.wait_until(max_attempts:10, delay:5) do |instance|
|
|
122
|
+
# instance.state.name == 'running'
|
|
123
|
+
# end
|
|
124
|
+
#
|
|
125
|
+
# ## Configuration
|
|
126
|
+
#
|
|
127
|
+
# You can configure the maximum number of polling attempts, and the
|
|
128
|
+
# delay (in seconds) between each polling attempt. The waiting condition is
|
|
129
|
+
# set by passing a block to {#wait_until}:
|
|
130
|
+
#
|
|
131
|
+
# # poll for ~25 seconds
|
|
132
|
+
# resource.wait_until(max_attempts:5,delay:5) {|resource|...}
|
|
133
|
+
#
|
|
134
|
+
# ## Callbacks
|
|
135
|
+
#
|
|
136
|
+
# You can be notified before each polling attempt and before each
|
|
137
|
+
# delay. If you throw `:success` or `:failure` from these callbacks,
|
|
138
|
+
# it will terminate the waiter.
|
|
139
|
+
#
|
|
140
|
+
# started_at = Time.now
|
|
141
|
+
# # poll for 1 hour, instead of a number of attempts
|
|
142
|
+
# proc = Proc.new do |attempts, response|
|
|
143
|
+
# throw :failure if Time.now - started_at > 3600
|
|
144
|
+
# end
|
|
145
|
+
#
|
|
146
|
+
# # disable max attempts
|
|
147
|
+
# instance.wait_until(before_wait:proc, max_attempts:nil) {...}
|
|
148
|
+
#
|
|
149
|
+
# ## Handling Errors
|
|
150
|
+
#
|
|
151
|
+
# When a waiter is successful, it returns the Resource. When a waiter
|
|
152
|
+
# fails, it raises an error.
|
|
153
|
+
#
|
|
154
|
+
# begin
|
|
155
|
+
# resource.wait_until(...)
|
|
156
|
+
# rescue Aws::Waiters::Errors::WaiterFailed
|
|
157
|
+
# # resource did not enter the desired state in time
|
|
158
|
+
# end
|
|
159
|
+
#
|
|
160
|
+
# @yieldparam [Resource] resource to be used in the waiting condition.
|
|
161
|
+
#
|
|
162
|
+
# @raise [Aws::Waiters::Errors::FailureStateError] Raised when the waiter
|
|
163
|
+
# terminates because the waiter has entered a state that it will not
|
|
164
|
+
# transition out of, preventing success.
|
|
165
|
+
#
|
|
166
|
+
# yet successful.
|
|
167
|
+
#
|
|
168
|
+
# @raise [Aws::Waiters::Errors::UnexpectedError] Raised when an error is
|
|
169
|
+
# encountered while polling for a resource that is not expected.
|
|
170
|
+
#
|
|
171
|
+
# @raise [NotImplementedError] Raised when the resource does not
|
|
172
|
+
#
|
|
173
|
+
# @option options [Integer] :max_attempts (10) Maximum number of
|
|
174
|
+
# attempts
|
|
175
|
+
# @option options [Integer] :delay (10) Delay between each
|
|
176
|
+
# attempt in seconds
|
|
177
|
+
# @option options [Proc] :before_attempt (nil) Callback
|
|
178
|
+
# invoked before each attempt
|
|
179
|
+
# @option options [Proc] :before_wait (nil) Callback
|
|
180
|
+
# invoked before each wait
|
|
181
|
+
# @return [Resource] if the waiter was successful
|
|
182
|
+
def wait_until(options = {}, &block)
|
|
183
|
+
self_copy = self.dup
|
|
184
|
+
attempts = 0
|
|
185
|
+
options[:max_attempts] = 10 unless options.key?(:max_attempts)
|
|
186
|
+
options[:delay] ||= 10
|
|
187
|
+
options[:poller] = Proc.new do
|
|
188
|
+
attempts += 1
|
|
189
|
+
if block.call(self_copy)
|
|
190
|
+
[:success, self_copy]
|
|
191
|
+
else
|
|
192
|
+
self_copy.reload unless attempts == options[:max_attempts]
|
|
193
|
+
:retry
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
Aws::Waiters::Waiter.new(options).wait({})
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @!group Actions
|
|
200
|
+
|
|
201
|
+
# @example Request syntax with placeholder values
|
|
202
|
+
#
|
|
203
|
+
# object_acl.put({
|
|
204
|
+
# acl: "private", # accepts private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control
|
|
205
|
+
# access_control_policy: {
|
|
206
|
+
# grants: [
|
|
207
|
+
# {
|
|
208
|
+
# grantee: {
|
|
209
|
+
# display_name: "DisplayName",
|
|
210
|
+
# email_address: "EmailAddress",
|
|
211
|
+
# id: "ID",
|
|
212
|
+
# type: "CanonicalUser", # required, accepts CanonicalUser, AmazonCustomerByEmail, Group
|
|
213
|
+
# uri: "URI",
|
|
214
|
+
# },
|
|
215
|
+
# permission: "FULL_CONTROL", # accepts FULL_CONTROL, WRITE, WRITE_ACP, READ, READ_ACP
|
|
216
|
+
# },
|
|
217
|
+
# ],
|
|
218
|
+
# owner: {
|
|
219
|
+
# display_name: "DisplayName",
|
|
220
|
+
# id: "ID",
|
|
221
|
+
# },
|
|
222
|
+
# },
|
|
223
|
+
# content_md5: "ContentMD5",
|
|
224
|
+
# grant_full_control: "GrantFullControl",
|
|
225
|
+
# grant_read: "GrantRead",
|
|
226
|
+
# grant_read_acp: "GrantReadACP",
|
|
227
|
+
# grant_write: "GrantWrite",
|
|
228
|
+
# grant_write_acp: "GrantWriteACP",
|
|
229
|
+
# request_payer: "requester", # accepts requester
|
|
230
|
+
# version_id: "ObjectVersionId",
|
|
231
|
+
# })
|
|
232
|
+
# @param [Hash] options ({})
|
|
233
|
+
# @option options [String] :acl
|
|
234
|
+
# The canned ACL to apply to the object. For more information, see
|
|
235
|
+
# [Canned ACL][1].
|
|
236
|
+
#
|
|
237
|
+
#
|
|
238
|
+
#
|
|
239
|
+
# [1]: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#CannedACL
|
|
240
|
+
# @option options [Types::AccessControlPolicy] :access_control_policy
|
|
241
|
+
# Contains the elements that set the ACL permissions for an object per
|
|
242
|
+
# grantee.
|
|
243
|
+
# @option options [String] :content_md5
|
|
244
|
+
# The base64-encoded 128-bit MD5 digest of the data. This header must be
|
|
245
|
+
# used as a message integrity check to verify that the request body was
|
|
246
|
+
# not corrupted in transit. For more information, go to [RFC
|
|
247
|
+
# 1864.>][1]
|
|
248
|
+
#
|
|
249
|
+
#
|
|
250
|
+
#
|
|
251
|
+
# [1]: http://www.ietf.org/rfc/rfc1864.txt
|
|
252
|
+
# @option options [String] :grant_full_control
|
|
253
|
+
# Allows grantee the read, write, read ACP, and write ACP permissions on
|
|
254
|
+
# the bucket.
|
|
255
|
+
# @option options [String] :grant_read
|
|
256
|
+
# Allows grantee to list the objects in the bucket.
|
|
257
|
+
# @option options [String] :grant_read_acp
|
|
258
|
+
# Allows grantee to read the bucket ACL.
|
|
259
|
+
# @option options [String] :grant_write
|
|
260
|
+
# Allows grantee to create, overwrite, and delete any object in the
|
|
261
|
+
# bucket.
|
|
262
|
+
# @option options [String] :grant_write_acp
|
|
263
|
+
# Allows grantee to write the ACL for the applicable bucket.
|
|
264
|
+
# @option options [String] :request_payer
|
|
265
|
+
# Confirms that the requester knows that they will be charged for the
|
|
266
|
+
# request. Bucket owners need not specify this parameter in their
|
|
267
|
+
# requests. For information about downloading objects from requester
|
|
268
|
+
# pays buckets, see [Downloading Objects in Requestor Pays Buckets][1]
|
|
269
|
+
# in the *Amazon S3 Developer Guide*.
|
|
270
|
+
#
|
|
271
|
+
#
|
|
272
|
+
#
|
|
273
|
+
# [1]: https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectsinRequesterPaysBuckets.html
|
|
274
|
+
# @option options [String] :version_id
|
|
275
|
+
# VersionId used to reference a specific version of the object.
|
|
276
|
+
# @return [Types::PutObjectAclOutput]
|
|
277
|
+
def put(options = {})
|
|
278
|
+
options = options.merge(
|
|
279
|
+
bucket: @bucket_name,
|
|
280
|
+
key: @object_key
|
|
281
|
+
)
|
|
282
|
+
resp = @client.put_object_acl(options)
|
|
283
|
+
resp.data
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @!group Associations
|
|
287
|
+
|
|
288
|
+
# @return [Object]
|
|
289
|
+
def object
|
|
290
|
+
Object.new(
|
|
291
|
+
bucket_name: @bucket_name,
|
|
292
|
+
key: @object_key,
|
|
293
|
+
client: @client
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# @deprecated
|
|
298
|
+
# @api private
|
|
299
|
+
def identifiers
|
|
300
|
+
{
|
|
301
|
+
bucket_name: @bucket_name,
|
|
302
|
+
object_key: @object_key
|
|
303
|
+
}
|
|
304
|
+
end
|
|
305
|
+
deprecated(:identifiers)
|
|
306
|
+
|
|
307
|
+
private
|
|
308
|
+
|
|
309
|
+
def extract_bucket_name(args, options)
|
|
310
|
+
value = args[0] || options.delete(:bucket_name)
|
|
311
|
+
case value
|
|
312
|
+
when String then value
|
|
313
|
+
when nil then raise ArgumentError, "missing required option :bucket_name"
|
|
314
|
+
else
|
|
315
|
+
msg = "expected :bucket_name to be a String, got #{value.class}"
|
|
316
|
+
raise ArgumentError, msg
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def extract_object_key(args, options)
|
|
321
|
+
value = args[1] || options.delete(:object_key)
|
|
322
|
+
case value
|
|
323
|
+
when String then value
|
|
324
|
+
when nil then raise ArgumentError, "missing required option :object_key"
|
|
325
|
+
else
|
|
326
|
+
msg = "expected :object_key to be a String, got #{value.class}"
|
|
327
|
+
raise ArgumentError, msg
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
class Collection < Aws::Resources::Collection; end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thread'
|
|
4
|
+
|
|
5
|
+
module Aws
|
|
6
|
+
module S3
|
|
7
|
+
# @api private
|
|
8
|
+
class ObjectCopier
|
|
9
|
+
|
|
10
|
+
# @param [S3::Object] object
|
|
11
|
+
def initialize(object, options = {})
|
|
12
|
+
@object = object
|
|
13
|
+
@options = options.merge(client: @object.client)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def copy_from(source, options = {})
|
|
17
|
+
copy_object(source, @object, merge_options(source, options))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def copy_to(target, options = {})
|
|
21
|
+
copy_object(@object, target, merge_options(target, options))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def copy_object(source, target, options)
|
|
27
|
+
target_bucket, target_key = copy_target(target)
|
|
28
|
+
options[:bucket] = target_bucket
|
|
29
|
+
options[:key] = target_key
|
|
30
|
+
options[:copy_source] = copy_source(source)
|
|
31
|
+
if options.delete(:multipart_copy)
|
|
32
|
+
apply_source_client(source, options)
|
|
33
|
+
ObjectMultipartCopier.new(@options).copy(options)
|
|
34
|
+
else
|
|
35
|
+
@object.client.copy_object(options)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def copy_source(source)
|
|
40
|
+
case source
|
|
41
|
+
when String then source
|
|
42
|
+
when Hash
|
|
43
|
+
src = "#{source[:bucket]}/#{escape(source[:key])}"
|
|
44
|
+
src += "?versionId=#{source[:version_id]}" if source.key?(:version_id)
|
|
45
|
+
src
|
|
46
|
+
when S3::Object, S3::ObjectSummary
|
|
47
|
+
"#{source.bucket_name}/#{escape(source.key)}"
|
|
48
|
+
when S3::ObjectVersion
|
|
49
|
+
"#{source.bucket_name}/#{escape(source.object_key)}?versionId=#{source.id}"
|
|
50
|
+
else
|
|
51
|
+
msg = "expected source to be an Aws::S3::Object, Hash, or String"
|
|
52
|
+
raise ArgumentError, msg
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def copy_target(target)
|
|
57
|
+
case target
|
|
58
|
+
when String then target.match(/([^\/]+?)\/(.+)/)[1,2]
|
|
59
|
+
when Hash then target.values_at(:bucket, :key)
|
|
60
|
+
when S3::Object then [target.bucket_name, target.key]
|
|
61
|
+
else
|
|
62
|
+
msg = "expected target to be an Aws::S3::Object, Hash, or String"
|
|
63
|
+
raise ArgumentError, msg
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def merge_options(source_or_target, options)
|
|
68
|
+
if Hash === source_or_target
|
|
69
|
+
source_or_target.inject(options.dup) do |opts, (key, value)|
|
|
70
|
+
opts[key] = value unless [:bucket, :key, :version_id].include?(key)
|
|
71
|
+
opts
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
options.dup
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def apply_source_client(source, options)
|
|
79
|
+
|
|
80
|
+
if source.respond_to?(:client)
|
|
81
|
+
options[:copy_source_client] ||= source.client
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if options[:copy_source_region]
|
|
85
|
+
config = @object.client.config
|
|
86
|
+
config = config.each_pair.inject({}) { |h, (k,v)| h[k] = v; h }
|
|
87
|
+
config[:region] = options.delete(:copy_source_region)
|
|
88
|
+
options[:copy_source_client] ||= S3::Client.new(config)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
options[:copy_source_client] ||= @object.client
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def escape(str)
|
|
96
|
+
Seahorse::Util.uri_path_escape(str)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thread'
|
|
4
|
+
require 'cgi'
|
|
5
|
+
|
|
6
|
+
module Aws
|
|
7
|
+
module S3
|
|
8
|
+
# @api private
|
|
9
|
+
class ObjectMultipartCopier
|
|
10
|
+
|
|
11
|
+
FIVE_MB = 5 * 1024 * 1024 # 5MB
|
|
12
|
+
|
|
13
|
+
FILE_TOO_SMALL = "unable to multipart copy files smaller than 5MB"
|
|
14
|
+
|
|
15
|
+
MAX_PARTS = 10_000
|
|
16
|
+
|
|
17
|
+
# @option options [Client] :client
|
|
18
|
+
# @option [Integer] :min_part_size (52428800) Size of copied parts.
|
|
19
|
+
# Defaults to 50MB.
|
|
20
|
+
# will be constructed from the given `options' hash.
|
|
21
|
+
# @option [Integer] :thread_count (10) Number of concurrent threads to
|
|
22
|
+
# use for copying parts.
|
|
23
|
+
def initialize(options = {})
|
|
24
|
+
@thread_count = options.delete(:thread_count) || 10
|
|
25
|
+
@min_part_size = options.delete(:min_part_size) || (FIVE_MB * 10)
|
|
26
|
+
@client = options[:client] || Client.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Client]
|
|
30
|
+
attr_reader :client
|
|
31
|
+
|
|
32
|
+
# @option (see S3::Client#copy_object)
|
|
33
|
+
def copy(options = {})
|
|
34
|
+
size = source_size(options)
|
|
35
|
+
options[:upload_id] = initiate_upload(options)
|
|
36
|
+
begin
|
|
37
|
+
parts = copy_parts(size, default_part_size(size), options)
|
|
38
|
+
complete_upload(parts, options)
|
|
39
|
+
rescue => error
|
|
40
|
+
abort_upload(options)
|
|
41
|
+
raise error
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def initiate_upload(options)
|
|
48
|
+
options = options_for(:create_multipart_upload, options)
|
|
49
|
+
@client.create_multipart_upload(options).upload_id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def copy_parts(size, default_part_size, options)
|
|
53
|
+
queue = PartQueue.new(compute_parts(size, default_part_size, options))
|
|
54
|
+
threads = []
|
|
55
|
+
@thread_count.times do
|
|
56
|
+
threads << copy_part_thread(queue)
|
|
57
|
+
end
|
|
58
|
+
threads.map(&:value).flatten.sort_by{ |part| part[:part_number] }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def copy_part_thread(queue)
|
|
62
|
+
Thread.new do
|
|
63
|
+
begin
|
|
64
|
+
completed = []
|
|
65
|
+
while part = queue.shift
|
|
66
|
+
completed << copy_part(part)
|
|
67
|
+
end
|
|
68
|
+
completed
|
|
69
|
+
rescue => error
|
|
70
|
+
queue.clear!
|
|
71
|
+
raise error
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def copy_part(part)
|
|
77
|
+
{
|
|
78
|
+
etag: @client.upload_part_copy(part).copy_part_result.etag,
|
|
79
|
+
part_number: part[:part_number],
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def complete_upload(parts, options)
|
|
84
|
+
options = options_for(:complete_multipart_upload, options)
|
|
85
|
+
options[:multipart_upload] = { parts: parts }
|
|
86
|
+
@client.complete_multipart_upload(options)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def abort_upload(options)
|
|
90
|
+
@client.abort_multipart_upload({
|
|
91
|
+
bucket: options[:bucket],
|
|
92
|
+
key: options[:key],
|
|
93
|
+
upload_id: options[:upload_id],
|
|
94
|
+
})
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def compute_parts(size, default_part_size, options)
|
|
98
|
+
part_number = 1
|
|
99
|
+
offset = 0
|
|
100
|
+
parts = []
|
|
101
|
+
options = options_for(:upload_part_copy, options)
|
|
102
|
+
while offset < size
|
|
103
|
+
parts << options.merge({
|
|
104
|
+
part_number: part_number,
|
|
105
|
+
copy_source_range: byte_range(offset, default_part_size, size),
|
|
106
|
+
})
|
|
107
|
+
part_number += 1
|
|
108
|
+
offset += default_part_size
|
|
109
|
+
end
|
|
110
|
+
parts
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def byte_range(offset, default_part_size, size)
|
|
114
|
+
if offset + default_part_size < size
|
|
115
|
+
"bytes=#{offset}-#{offset + default_part_size - 1}"
|
|
116
|
+
else
|
|
117
|
+
"bytes=#{offset}-#{size - 1}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def source_size(options)
|
|
122
|
+
return options.delete(:content_length) if options[:content_length]
|
|
123
|
+
|
|
124
|
+
client = options[:copy_source_client] || @client
|
|
125
|
+
|
|
126
|
+
if vid_match = options[:copy_source].match(/([^\/]+?)\/(.+)\?versionId=(.+)/)
|
|
127
|
+
bucket, key, version_id = vid_match[1,3]
|
|
128
|
+
else
|
|
129
|
+
bucket, key = options[:copy_source].match(/([^\/]+?)\/(.+)/)[1,2]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
key = CGI.unescape(key)
|
|
133
|
+
opts = { bucket: bucket, key: key }
|
|
134
|
+
opts[:version_id] = version_id if version_id
|
|
135
|
+
client.head_object(opts).content_length
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def default_part_size(source_size)
|
|
139
|
+
if source_size < FIVE_MB
|
|
140
|
+
raise ArgumentError, FILE_TOO_SMALL
|
|
141
|
+
else
|
|
142
|
+
[(source_size.to_f / MAX_PARTS).ceil, @min_part_size].max.to_i
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def options_for(operation_name, options)
|
|
147
|
+
API_OPTIONS[operation_name].inject({}) do |hash, opt_name|
|
|
148
|
+
hash[opt_name] = options[opt_name] if options.key?(opt_name)
|
|
149
|
+
hash
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @api private
|
|
154
|
+
def self.options_for(shape_name)
|
|
155
|
+
Client.api.metadata['shapes'][shape_name].member_names
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
API_OPTIONS = {
|
|
159
|
+
create_multipart_upload: Types::CreateMultipartUploadRequest.members,
|
|
160
|
+
upload_part_copy: Types::UploadPartCopyRequest.members,
|
|
161
|
+
complete_multipart_upload: Types::CompleteMultipartUploadRequest.members,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class PartQueue
|
|
165
|
+
|
|
166
|
+
def initialize(parts = [])
|
|
167
|
+
@parts = parts
|
|
168
|
+
@mutex = Mutex.new
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def shift
|
|
172
|
+
@mutex.synchronize { @parts.shift }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def clear!
|
|
176
|
+
@mutex.synchronize { @parts.clear }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|