aws-sdk 1.17.0 → 1.18.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: cfa7ad3c4cd9323dcff9fbe929ed06ecf5d4e9b3
4
- data.tar.gz: dca56fc5cf17f37524a67e0d1fcf873dcf3c062a
3
+ metadata.gz: 922eab0a8bd13d682f4ac39b61b26540803ba0bb
4
+ data.tar.gz: f4cbc9198f4fbd9b353e5869a140ecc6e5c76703
5
5
  SHA512:
6
- metadata.gz: ca1d78ff14270f1bf41e358718c1f76aa2e0f99527200566cbbc0515d6641120c0f7725e2d6b79f46c6cb851afd6382cc3e2b05c98fbfb168ff22df370aaf982
7
- data.tar.gz: 55d6dfcd5fed9b4ff3c1ba821c960db5572c0c9ab83309c3139b789a99ae5dc3c2be4bfb75e1c4cf735086b09f49d49ac53c6b4551d4c6cdcea94bcb9122ebd5
6
+ metadata.gz: ab1afa3d7bcace527f0d20a8c9cb769a4bc36dab589138f4dcf692fa86d014db312d15398e1c051ca150cb1927b1c3acabf15a7f350784f93ff55e06af7781d9
7
+ data.tar.gz: b74beeb8fbd0d50c9a4e1f6870a571e047a50f5d4e7b1d7000ab6ed4cd1a68ca9d9cee405543d510e8dc7715af544674bede3cd9120bdb1db531a94067cbc3c2
@@ -12,7 +12,7 @@
12
12
  # language governing permissions and limitations under the License.
13
13
 
14
14
  ---
15
- :api_version: '2013-07-15'
15
+ :api_version: '2013-08-15'
16
16
  :operations:
17
17
  - :name: ActivateLicense
18
18
  :method: :activate_license
@@ -2337,6 +2337,8 @@
2337
2337
  :children:
2338
2338
  start:
2339
2339
  :type: :time
2340
+ end:
2341
+ :type: :time
2340
2342
  duration:
2341
2343
  :type: :integer
2342
2344
  usagePrice:
@@ -2417,6 +2419,57 @@
2417
2419
  item:
2418
2420
  :rename: :tag_set
2419
2421
  :list: true
2422
+ - :name: DescribeReservedInstancesModifications
2423
+ :method: :describe_reserved_instances_modifications
2424
+ :inputs:
2425
+ ReservedInstancesModificationId:
2426
+ - :list:
2427
+ - :string
2428
+ - :rename: ReservedInstancesModificationIds
2429
+ NextToken:
2430
+ - :string
2431
+ Filter:
2432
+ - :list:
2433
+ - :structure:
2434
+ Name:
2435
+ - :string
2436
+ Value:
2437
+ - :list:
2438
+ - :string
2439
+ - :rename: Values
2440
+ - :rename: filters
2441
+ :outputs:
2442
+ :children:
2443
+ reservedInstancesModificationsSet:
2444
+ :ignore: true
2445
+ :children:
2446
+ item:
2447
+ :rename: :reserved_instances_modifications_set
2448
+ :list: true
2449
+ :children:
2450
+ reservedInstancesSet:
2451
+ :ignore: true
2452
+ :children:
2453
+ item:
2454
+ :rename: :reserved_instances_set
2455
+ :list: true
2456
+ modificationResultSet:
2457
+ :ignore: true
2458
+ :children:
2459
+ item:
2460
+ :rename: :modification_result_set
2461
+ :list: true
2462
+ :children:
2463
+ targetConfiguration:
2464
+ :children:
2465
+ instanceCount:
2466
+ :type: :integer
2467
+ createDate:
2468
+ :type: :time
2469
+ updateDate:
2470
+ :type: :time
2471
+ effectiveDate:
2472
+ :type: :time
2420
2473
  - :name: DescribeReservedInstancesOfferings
2421
2474
  :method: :describe_reserved_instances_offerings
2422
2475
  :inputs:
@@ -3671,6 +3724,28 @@
3671
3724
  DeleteOnTermination:
3672
3725
  - :boolean
3673
3726
  :outputs: {}
3727
+ - :name: ModifyReservedInstances
3728
+ :method: :modify_reserved_instances
3729
+ :inputs:
3730
+ ClientToken:
3731
+ - :string
3732
+ ReservedInstancesId:
3733
+ - :list:
3734
+ - :string
3735
+ - :required
3736
+ - :rename: ReservedInstancesIds
3737
+ ReservedInstancesConfigurationSetItemType:
3738
+ - :list:
3739
+ - :structure:
3740
+ AvailabilityZone:
3741
+ - :string
3742
+ Platform:
3743
+ - :string
3744
+ InstanceCount:
3745
+ - :integer
3746
+ - :required
3747
+ - :rename: targetConfigurations
3748
+ :outputs: {}
3674
3749
  - :name: ModifySnapshotAttribute
3675
3750
  :method: :modify_snapshot_attribute
3676
3751
  :inputs:
@@ -36,6 +36,8 @@
36
36
  - :string
37
37
  Region:
38
38
  - :string
39
+ VpcId:
40
+ - :string
39
41
  Attributes:
40
42
  - :map:
41
43
  :key:
@@ -53,6 +55,8 @@
53
55
  - :string
54
56
  DefaultAvailabilityZone:
55
57
  - :string
58
+ DefaultSubnetId:
59
+ - :string
56
60
  CustomJson:
57
61
  - :string
58
62
  ConfigurationManager:
@@ -202,6 +206,8 @@
202
206
  - :string
203
207
  AvailabilityZone:
204
208
  - :string
209
+ SubnetId:
210
+ - :string
205
211
  Architecture:
206
212
  - :string
207
213
  RootDeviceType:
@@ -291,6 +297,8 @@
291
297
  Region:
292
298
  - :string
293
299
  - :required
300
+ VpcId:
301
+ - :string
294
302
  Attributes:
295
303
  - :map:
296
304
  :key:
@@ -309,6 +317,8 @@
309
317
  - :string
310
318
  DefaultAvailabilityZone:
311
319
  - :string
320
+ DefaultSubnetId:
321
+ - :string
312
322
  CustomJson:
313
323
  - :string
314
324
  ConfigurationManager:
@@ -640,6 +650,9 @@
640
650
  AvailabilityZones:
641
651
  :sym: :availability_zones
642
652
  :type: :string
653
+ SubnetIds:
654
+ :sym: :subnet_ids
655
+ :type: :string
643
656
  Ec2InstanceIds:
644
657
  :sym: :ec_2_instance_ids
645
658
  :type: :string
@@ -694,6 +707,9 @@
694
707
  AvailabilityZone:
695
708
  :sym: :availability_zone
696
709
  :type: :string
710
+ SubnetId:
711
+ :sym: :subnet_id
712
+ :type: :string
697
713
  PublicDns:
698
714
  :sym: :public_dns
699
715
  :type: :string
@@ -1036,6 +1052,9 @@
1036
1052
  Region:
1037
1053
  :sym: :region
1038
1054
  :type: :string
1055
+ VpcId:
1056
+ :sym: :vpc_id
1057
+ :type: :string
1039
1058
  Attributes:
1040
1059
  :sym: :attributes
1041
1060
  :type: :map
@@ -1057,6 +1076,9 @@
1057
1076
  DefaultAvailabilityZone:
1058
1077
  :sym: :default_availability_zone
1059
1078
  :type: :string
1079
+ DefaultSubnetId:
1080
+ :sym: :default_subnet_id
1081
+ :type: :string
1060
1082
  CustomJson:
1061
1083
  :sym: :custom_json
1062
1084
  :type: :string
@@ -1557,6 +1579,8 @@
1557
1579
  - :string
1558
1580
  DefaultAvailabilityZone:
1559
1581
  - :string
1582
+ DefaultSubnetId:
1583
+ - :string
1560
1584
  CustomJson:
1561
1585
  - :string
1562
1586
  ConfigurationManager:
@@ -581,8 +581,12 @@ module AWS
581
581
  # @return [Array<Symbol>] Returns a list of service operations as
582
582
  # method names supported by this client.
583
583
  # @api private
584
- def operations
585
- @operations ||= []
584
+ def operations(options = {})
585
+ if name.match(/V\d{8}$/)
586
+ @operations ||= []
587
+ else
588
+ client_class(options).operations
589
+ end
586
590
  end
587
591
 
588
592
  # @api private
@@ -17,7 +17,7 @@ module AWS
17
17
  # Client class for Amazon Elastic Compute Cloud (EC2).
18
18
  class Client < Core::QueryClient
19
19
 
20
- API_VERSION = '2013-07-15'
20
+ API_VERSION = '2013-08-15'
21
21
 
22
22
  # @api private
23
23
  CACHEABLE_REQUESTS = Set[
@@ -60,9 +60,9 @@ module AWS
60
60
 
61
61
  end
62
62
 
63
- class Client::V20130715 < Client
63
+ class Client::V20130815 < Client
64
64
 
65
- define_client_methods('2013-07-15')
65
+ define_client_methods('2013-08-15')
66
66
 
67
67
  end
68
68
 
@@ -50,11 +50,11 @@ module AWS
50
50
 
51
51
  populates_from(:list_vaults) do |resp|
52
52
  resp.request_options[:account_id] == account_id and
53
- resp[:vault_list].find {|vault| vault[:name] == name }
53
+ resp[:vault_list].find {|vault| vault[:vault_name] == name }
54
54
  end
55
55
 
56
56
  populates_from(:describe_vault) do |resp|
57
- if resp.request_options[:account_id] == account_id and resp[:name] == name
57
+ if resp.request_options[:account_id] == account_id and resp[:vault_name] == name
58
58
  resp
59
59
  end
60
60
  end
data/lib/aws/s3/client.rb CHANGED
@@ -99,678 +99,943 @@ module AWS
99
99
  end
100
100
  end
101
101
 
102
- def self.object_method(method_name, verb, *args, &block)
103
- bucket_method(method_name, verb, *args) do
104
- configure_request do |req, options|
105
- validate_key!(options[:key])
106
- super(req, options)
107
- req.key = options[:key]
108
- end
102
+ protected
109
103
 
110
- instance_eval(&block) if block
104
+ def extract_error_details response
105
+ if
106
+ (response.http_response.status >= 300 ||
107
+ response.request_type == :complete_multipart_upload) and
108
+ body = response.http_response.body and
109
+ error = Core::XML::Parser.parse(body) and
110
+ error[:code]
111
+ then
112
+ [error[:code], error[:message]]
111
113
  end
112
114
  end
113
115
 
114
- public
115
-
116
- # Creates a bucket.
117
- # @overload create_bucket(options = {})
118
- # @param [Hash] options
119
- # @option options [required,String] :bucket_name
120
- # @option options [String] :acl A canned ACL (e.g. 'private',
121
- # 'public-read', etc). See the S3 API documentation for
122
- # a complete list of valid values.
123
- # @option options [String] :grant_read
124
- # @option options [String] :grant_write
125
- # @option options [String] :grant_read_acp
126
- # @option options [String] :grant_write_acp
127
- # @option options [String] :grant_full_control
128
- # @return [Core::Response]
129
- bucket_method(:create_bucket, :put, :header_options => {
130
- :acl => 'x-amz-acl',
131
- :grant_read => 'x-amz-grant-read',
132
- :grant_write => 'x-amz-grant-write',
133
- :grant_read_acp => 'x-amz-grant-read-acp',
134
- :grant_write_acp => 'x-amz-grant-write-acp',
135
- :grant_full_control => 'x-amz-grant-full-control',
136
- }) do
116
+ def empty_response_body? response_body
117
+ response_body.nil? or response_body == ''
118
+ end
137
119
 
138
- configure_request do |req, options|
139
- validate_bucket_name!(options[:bucket_name])
140
- if location = options[:location_constraint]
141
- xmlns = "http://s3.amazonaws.com/doc/#{API_VERSION}/"
142
- req.body = <<-XML
143
- <CreateBucketConfiguration xmlns="#{xmlns}">
144
- <LocationConstraint>#{location}</LocationConstraint>
145
- </CreateBucketConfiguration>
146
- XML
147
- end
148
- super(req, options)
120
+ # There are a few of s3 requests that can generate empty bodies and
121
+ # yet still be errors. These return empty bodies to comply with the
122
+ # HTTP spec. We have to detect these errors specially.
123
+ def populate_error resp
124
+ code = resp.http_response.status
125
+ if EMPTY_BODY_ERRORS.include?(code) and empty_response_body?(resp.http_response.body)
126
+ error_class = EMPTY_BODY_ERRORS[code]
127
+ resp.error = error_class.new(resp.http_request, resp.http_response)
128
+ else
129
+ super
149
130
  end
150
-
151
131
  end
152
- alias_method :put_bucket, :create_bucket
153
-
154
- # @!method put_bucket_website(options = {})
155
- # @param [Hash] options
156
- # @option (see WebsiteConfiguration#initialize)
157
- # @option options [required,String] :bucket_name
158
- # @return [Core::Response]
159
- bucket_method(:put_bucket_website, :put, 'website') do
160
132
 
161
- configure_request do |req, options|
162
- req.body = Nokogiri::XML::Builder.new do |xml|
163
- xml.WebsiteConfiguration(:xmlns => XMLNS) do
133
+ def retryable_error? response
134
+ super or
135
+ failed_multipart_upload?(response) or
136
+ response.error.is_a?(Errors::RequestTimeout)
137
+ end
164
138
 
165
- if redirect = options[:redirect_all_requests_to]
166
- xml.RedirectAllRequestsTo do
167
- xml.HostName(redirect[:host_name])
168
- xml.Protocol(redirect[:protocol]) if redirect[:protocol]
169
- end
170
- end
139
+ # S3 may return a 200 response code in response to complete_multipart_upload
140
+ # and then start streaming whitespace until it knows the final result.
141
+ # At that time it sends an XML message with success or failure.
142
+ def failed_multipart_upload? response
143
+ response.request_type == :complete_multipart_upload &&
144
+ extract_error_details(response)
145
+ end
171
146
 
172
- if indx = options[:index_document]
173
- xml.IndexDocument do
174
- xml.Suffix(indx[:suffix])
175
- end
176
- end
147
+ def new_request
148
+ req = S3::Request.new
149
+ req.force_path_style = config.s3_force_path_style?
150
+ req
151
+ end
177
152
 
178
- if err = options[:error_document]
179
- xml.ErrorDocument do
180
- xml.Key(err[:key])
181
- end
182
- end
153
+ # Previously the access control policy could be specified via :acl
154
+ # as a string or an object that responds to #to_xml. The prefered
155
+ # method now is to pass :access_control_policy an xml document.
156
+ def move_access_control_policy options
157
+ if acl = options[:acl]
158
+ if acl.is_a?(String) and is_xml?(acl)
159
+ options[:access_control_policy] = options.delete(:acl)
160
+ elsif acl.respond_to?(:to_xml)
161
+ options[:access_control_policy] = options.delete(:acl).to_xml
162
+ end
163
+ end
164
+ end
183
165
 
184
- rules = options[:routing_rules]
185
- if rules.is_a?(Array) && !rules.empty?
186
- xml.RoutingRules do
187
- rules.each do |rule|
188
- xml.RoutingRule do
166
+ # @param [String] possible_xml
167
+ # @return [Boolean] Returns `true` if the given string is a valid xml
168
+ # document.
169
+ def is_xml? possible_xml
170
+ begin
171
+ REXML::Document.new(possible_xml).has_elements?
172
+ rescue
173
+ false
174
+ end
175
+ end
189
176
 
190
- redirect = rule[:redirect]
191
- xml.Redirect do
192
- xml.Protocol(redirect[:protocol]) if redirect[:protocol]
193
- xml.HostName(redirect[:host_name]) if redirect[:host_name]
194
- xml.ReplaceKeyPrefixWith(redirect[:replace_key_prefix_with]) if redirect[:replace_key_prefix_with]
195
- xml.ReplaceKeyWith(redirect[:replace_key_with]) if redirect[:replace_key_with]
196
- xml.HttpRedirectCode(redirect[:http_redirect_code]) if redirect[:http_redirect_code]
197
- end
177
+ def md5 str
178
+ Base64.encode64(Digest::MD5.digest(str)).strip
179
+ end
198
180
 
199
- if condition = rule[:condition]
200
- xml.Condition do
201
- xml.KeyPrefixEquals(condition[:key_prefix_equals]) if condition[:key_prefix_equals]
202
- xml.HttpErrorCodeReturnedEquals(condition[:http_error_code_returned_equals]) if condition[:http_error_code_returned_equals]
203
- end
204
- end
181
+ def parse_copy_part_response resp
182
+ doc = REXML::Document.new(resp.http_response.body)
183
+ resp[:etag] = doc.root.elements["ETag"].text
184
+ resp[:last_modified] = doc.root.elements["LastModified"].text
185
+ if header = resp.http_response.headers['x-amzn-requestid']
186
+ data[:request_id] = [header].flatten.first
187
+ end
188
+ end
205
189
 
206
- end
207
- end
208
- end
209
- end
190
+ def extract_object_headers resp
191
+ meta = {}
192
+ resp.http_response.headers.each_pair do |name,value|
193
+ if name =~ /^x-amz-meta-(.+)$/i
194
+ meta[$1] = [value].flatten.join
195
+ end
196
+ end
197
+ resp.data[:meta] = meta
210
198
 
211
- end
212
- end.doc.root.to_xml
213
- super(req, options)
199
+ if expiry = resp.http_response.headers['x-amz-expiration']
200
+ expiry.first =~ /^expiry-date="(.+)", rule-id="(.+)"$/
201
+ exp_date = DateTime.parse($1)
202
+ exp_rule_id = $2
203
+ else
204
+ exp_date = nil
205
+ exp_rule_id = nil
214
206
  end
207
+ resp.data[:expiration_date] = exp_date if exp_date
208
+ resp.data[:expiration_rule_id] = exp_rule_id if exp_rule_id
215
209
 
216
- end
210
+ restoring = false
211
+ restore_date = nil
217
212
 
218
- # @overload get_bucket_website(options = {})
219
- # @param [Hash] options
220
- # @option options [required,String] :bucket_name
221
- # @return [Core::Response]
222
- # * `:index_document` - (Hash)
223
- # * `:suffix` - (String)
224
- # * `:error_document` - (Hash)
225
- # * `:key` - (String)
226
- bucket_method(:get_bucket_website, :get, 'website', XML::GetBucketWebsite)
213
+ if restore = resp.http_response.headers['x-amz-restore']
214
+ if restore.first =~ /ongoing-request="(.+?)", expiry-date="(.+?)"/
215
+ restoring = $1 == "true"
216
+ restore_date = $2 && DateTime.parse($2)
217
+ elsif restore.first =~ /ongoing-request="(.+?)"/
218
+ restoring = $1 == "true"
219
+ end
220
+ end
221
+ resp.data[:restore_in_progress] = restoring
222
+ resp.data[:restore_expiration_date] = restore_date if restore_date
227
223
 
228
- # @overload delete_bucket_website(options = {})
229
- # @param [Hash] options
230
- # @option options [required,String] :bucket_name
231
- # @return [Core::Response]
232
- bucket_method(:delete_bucket_website, :delete, 'website')
224
+ {
225
+ 'x-amz-version-id' => :version_id,
226
+ 'content-type' => :content_type,
227
+ 'content-encoding' => :content_encoding,
228
+ 'cache-control' => :cache_control,
229
+ 'expires' => :expires,
230
+ 'etag' => :etag,
231
+ 'x-amz-website-redirect-location' => :website_redirect_location,
232
+ 'accept-ranges' => :accept_ranges,
233
+ }.each_pair do |header,method|
234
+ if value = resp.http_response.header(header)
235
+ resp.data[method] = value
236
+ end
237
+ end
233
238
 
234
- # Deletes an empty bucket.
235
- # @overload delete_bucket(options = {})
236
- # @param [Hash] options
237
- # @option options [required,String] :bucket_name
238
- # @return [Core::Response]
239
- bucket_method(:delete_bucket, :delete)
239
+ if time = resp.http_response.header('Last-Modified')
240
+ resp.data[:last_modified] = Time.parse(time)
241
+ end
240
242
 
241
- # @overload set_bucket_lifecycle_configuration(options = {})
242
- # @param [Hash] options
243
- # @option options [required,String] :bucket_name
244
- # @option options [required,String] :lifecycle_configuration
245
- # @return [Core::Response]
246
- bucket_method(:set_bucket_lifecycle_configuration, :put) do
243
+ if length = resp.http_response.header('content-length')
244
+ resp.data[:content_length] = length.to_i
245
+ end
247
246
 
248
- configure_request do |req, options|
249
- xml = options[:lifecycle_configuration]
250
- req.add_param('lifecycle')
251
- req.body = xml
252
- req.headers['content-md5'] = md5(xml)
253
- super(req, options)
247
+ if sse = resp.http_response.header('x-amz-server-side-encryption')
248
+ resp.data[:server_side_encryption] = sse.downcase.to_sym
254
249
  end
255
250
 
256
251
  end
257
252
 
258
- # @overload get_bucket_lifecycle_configuration(options = {})
259
- # @param [Hash] options
260
- # @option options [required,String] :bucket_name
261
- # @return [Core::Response]
262
- bucket_method(:get_bucket_lifecycle_configuration, :get) do
263
-
264
- configure_request do |req, options|
265
- req.add_param('lifecycle')
266
- super(req, options)
267
- end
268
-
269
- process_response do |resp|
270
- xml = resp.http_response.body
271
- resp.data = XML::GetBucketLifecycleConfiguration.parse(xml)
272
- end
273
-
274
- end
275
-
276
- # @overload delete_bucket_lifecycle_configuration(options = {})
277
- # @param [Hash] options
278
- # @option options [required,String] :bucket_name
279
- # @return [Core::Response]
280
- bucket_method(:delete_bucket_lifecycle_configuration, :delete) do
253
+ module Validators
281
254
 
282
- configure_request do |req, options|
283
- req.add_param('lifecycle')
284
- super(req, options)
255
+ # @return [Boolean] Returns true if the given bucket name is valid.
256
+ def valid_bucket_name?(bucket_name)
257
+ validate_bucket_name!(bucket_name) rescue false
285
258
  end
286
259
 
287
- end
288
-
289
- # @overload put_bucket_cors(options = {})
290
- # @param [Hash] options
291
- # @option options [required,String] :bucket_name
292
- # @option options [required,Array<Hash>] :rules An array of rule hashes.
293
- # * `:id` - (String) A unique identifier for the rule. The ID
294
- # value can be up to 255 characters long. The IDs help you find
295
- # a rule in the configuration.
296
- # * `:allowed_methods` - (required,Array<String>) A list of HTTP
297
- # methods that you want to allow the origin to execute.
298
- # Each rule must identify at least one method.
299
- # * `:allowed_origins` - (required,Array<String>) A list of origins
300
- # you want to allow cross-domain requests from. This can
301
- # contain at most one * wild character.
302
- # * `:allowed_headers` - (Array<String>) A list of headers allowed
303
- # in a pre-flight OPTIONS request via the
304
- # Access-Control-Request-Headers header. Each header name
305
- # specified in the Access-Control-Request-Headers header must
306
- # have a corresponding entry in the rule.
307
- # Amazon S3 will send only the allowed headers in a response
308
- # that were requested. This can contain at most one * wild
309
- # character.
310
- # * `:max_age_seconds` - (Integer) The time in seconds that your
311
- # browser is to cache the preflight response for the specified
312
- # resource.
313
- # * `:expose_headers` - (Array<String>) One or more headers in
314
- # the response that you want customers to be able to access
315
- # from their applications (for example, from a JavaScript
316
- # XMLHttpRequest object).
317
- # @return [Core::Response]
318
- bucket_method(:put_bucket_cors, :put) do
319
- configure_request do |req, options|
260
+ # Returns true if the given `bucket_name` is DNS compatible.
261
+ #
262
+ # DNS compatible bucket names may be accessed like:
263
+ #
264
+ # http://dns.compat.bucket.name.s3.amazonaws.com/
265
+ #
266
+ # Whereas non-dns compatible bucket names must place the bucket
267
+ # name in the url path, like:
268
+ #
269
+ # http://s3.amazonaws.com/dns_incompat_bucket_name/
270
+ #
271
+ # @return [Boolean] Returns true if the given bucket name may be
272
+ # is dns compatible.
273
+ # this bucket n
274
+ #
275
+ def dns_compatible_bucket_name?(bucket_name)
276
+ return false if
277
+ !valid_bucket_name?(bucket_name) or
320
278
 
321
- req.add_param('cors')
279
+ # Bucket names should be between 3 and 63 characters long
280
+ bucket_name.size > 63 or
322
281
 
323
- xml = Nokogiri::XML::Builder.new do |xml|
324
- xml.CORSConfiguration do
325
- options[:rules].each do |rule|
326
- xml.CORSRule do
282
+ # Bucket names must only contain lowercase letters, numbers, dots, and dashes
283
+ # and must start and end with a lowercase letter or a number
284
+ bucket_name !~ /^[a-z0-9][a-z0-9.-]+[a-z0-9]$/ or
327
285
 
328
- xml.ID(rule[:id]) if rule[:id]
286
+ # Bucket names should not be formatted like an IP address (e.g., 192.168.5.4)
287
+ bucket_name =~ /(\d+\.){3}\d+/ or
329
288
 
330
- (rule[:allowed_methods] || []).each do |method|
331
- xml.AllowedMethod(method)
332
- end
289
+ # Bucket names cannot contain two, adjacent periods
290
+ bucket_name['..'] or
333
291
 
334
- (rule[:allowed_origins] || []).each do |origin|
335
- xml.AllowedOrigin(origin)
336
- end
292
+ # Bucket names cannot contain dashes next to periods
293
+ # (e.g., "my-.bucket.com" and "my.-bucket" are invalid)
294
+ (bucket_name['-.'] || bucket_name['.-'])
337
295
 
338
- (rule[:allowed_headers] || []).each do |header|
339
- xml.AllowedHeader(header)
340
- end
296
+ true
297
+ end
341
298
 
342
- xml.MaxAgeSeconds(rule[:max_age_seconds]) if
343
- rule[:max_age_seconds]
299
+ # Returns true if the bucket name must be used in the request
300
+ # path instead of as a sub-domain when making requests against
301
+ # S3.
302
+ #
303
+ # This can be an issue if the bucket name is DNS compatible but
304
+ # contains '.' (periods). These cause the SSL certificate to
305
+ # become invalid when making authenticated requets over SSL to the
306
+ # bucket name. The solution is to send this as a path argument
307
+ # instead.
308
+ #
309
+ # @return [Boolean] Returns true if the bucket name should be used
310
+ # as a path segement instead of dns prefix when making requests
311
+ # against s3.
312
+ #
313
+ def path_style_bucket_name? bucket_name
314
+ if dns_compatible_bucket_name?(bucket_name)
315
+ bucket_name =~ /\./ ? true : false
316
+ else
317
+ true
318
+ end
319
+ end
344
320
 
345
- (rule[:expose_headers] || []).each do |header|
346
- xml.ExposeHeader(header)
347
- end
321
+ def validate! name, value, &block
322
+ if error_msg = yield
323
+ raise ArgumentError, "#{name} #{error_msg}"
324
+ end
325
+ value
326
+ end
348
327
 
349
- end
350
- end
328
+ def validate_key!(key)
329
+ validate!('key', key) do
330
+ case
331
+ when key.nil? || key == ''
332
+ 'may not be blank'
351
333
  end
352
- end.doc.root.to_xml
353
-
354
- req.body = xml
355
- req.headers['content-md5'] = md5(xml)
356
-
357
- super(req, options)
358
-
334
+ end
359
335
  end
360
- end
361
-
362
- # @overload get_bucket_cors(options = {})
363
- # @param [Hash] options
364
- # @option options [required,String] :bucket_name
365
- # @return [Core::Response]
366
- bucket_method(:get_bucket_cors, :get) do
367
336
 
368
- configure_request do |req, options|
369
- req.add_param('cors')
370
- super(req, options)
337
+ def require_bucket_name! bucket_name
338
+ if [nil, ''].include?(bucket_name)
339
+ raise ArgumentError, "bucket_name may not be blank"
340
+ end
371
341
  end
372
342
 
373
- process_response do |resp|
374
- resp.data = XML::GetBucketCors.parse(resp.http_response.body)
343
+ # Returns true if the given bucket name is valid. If the name
344
+ # is invalid, an ArgumentError is raised.
345
+ def validate_bucket_name!(bucket_name)
346
+ validate!('bucket_name', bucket_name) do
347
+ case
348
+ when bucket_name.nil? || bucket_name == ''
349
+ 'may not be blank'
350
+ when bucket_name !~ /^[A-Za-z0-9._\-]+$/
351
+ 'may only contain uppercase letters, lowercase letters, numbers, periods (.), ' +
352
+ 'underscores (_), and dashes (-)'
353
+ when !(3..255).include?(bucket_name.size)
354
+ 'must be between 3 and 255 characters long'
355
+ when bucket_name =~ /\n/
356
+ 'must not contain a newline character'
357
+ end
358
+ end
375
359
  end
376
360
 
377
- end
378
-
379
- # @overload delete_bucket_cors(options = {})
380
- # @param [Hash] options
381
- # @option options [required,String] :bucket_name
382
- # @return [Core::Response]
383
- bucket_method(:delete_bucket_cors, :delete) do
384
- configure_request do |req, options|
385
- req.add_param('cors')
386
- super(req, options)
361
+ def require_policy!(policy)
362
+ validate!('policy', policy) do
363
+ case
364
+ when policy.nil? || policy == ''
365
+ 'may not be blank'
366
+ else
367
+ json_validation_message(policy)
368
+ end
369
+ end
387
370
  end
388
- end
389
371
 
390
- # @overload put_bucket_tagging(options = {})
391
- # @param [Hash] options
392
- # @option options [required,String] :bucket_name
393
- # @option options [Hash] :tags
394
- # @return [Core::Response]
395
- bucket_method(:put_bucket_tagging, :put) do
396
- configure_request do |req, options|
372
+ def require_acl! options
373
+ acl_options = [
374
+ :acl,
375
+ :grant_read,
376
+ :grant_write,
377
+ :grant_read_acp,
378
+ :grant_write_acp,
379
+ :grant_full_control,
380
+ :access_control_policy,
381
+ ]
382
+ unless options.keys.any?{|opt| acl_options.include?(opt) }
383
+ msg = "missing a required ACL option, must provide an ACL " +
384
+ "via :acl, :grant_* or :access_control_policy"
385
+ raise ArgumentError, msg
386
+ end
387
+ end
397
388
 
398
- req.add_param('tagging')
389
+ def set_body_stream_and_content_length request, options
399
390
 
400
- xml = Nokogiri::XML::Builder.new
401
- xml.Tagging do |xml|
402
- xml.TagSet do
403
- options[:tags].each_pair do |key,value|
404
- xml.Tag do
405
- xml.Key(key)
406
- xml.Value(value)
407
- end
408
- end
409
- end
391
+ unless options[:content_length]
392
+ msg = "S3 requires a content-length header, unable to determine "
393
+ msg << "the content length of the data provided, please set "
394
+ msg << ":content_length"
395
+ raise ArgumentError, msg
410
396
  end
411
397
 
412
- xml = xml.doc.root.to_xml
413
- req.body = xml
414
- req.headers['content-md5'] = md5(xml)
415
-
416
- super(req, options)
398
+ request.headers['content-length'] = options[:content_length]
399
+ request.body_stream = options[:data]
417
400
 
418
401
  end
419
- end
420
402
 
421
- # @overload get_bucket_tagging(options = {})
422
- # @param [Hash] options
423
- # @option options [required,String] :bucket_name
424
- # @return [Core::Response]
425
- bucket_method(:get_bucket_tagging, :get) do
403
+ def require_upload_id!(upload_id)
404
+ validate!("upload_id", upload_id) do
405
+ "must not be blank" if upload_id.to_s.empty?
406
+ end
407
+ end
426
408
 
427
- configure_request do |req, options|
428
- req.add_param('tagging')
429
- super(req, options)
409
+ def require_part_number! part_number
410
+ validate!("part_number", part_number) do
411
+ "must not be blank" if part_number.to_s.empty?
412
+ end
430
413
  end
431
414
 
432
- process_response do |resp|
433
- resp.data = XML::GetBucketTagging.parse(resp.http_response.body)
415
+ def validate_parts!(parts)
416
+ validate!("parts", parts) do
417
+ if !parts.kind_of?(Array)
418
+ "must not be blank"
419
+ elsif parts.empty?
420
+ "must contain at least one entry"
421
+ elsif !parts.all? { |p| p.kind_of?(Hash) }
422
+ "must be an array of hashes"
423
+ elsif !parts.all? { |p| p[:part_number] }
424
+ "must contain part_number for each part"
425
+ elsif !parts.all? { |p| p[:etag] }
426
+ "must contain etag for each part"
427
+ elsif parts.any? { |p| p[:part_number].to_i < 1 }
428
+ "must not have part numbers less than 1"
429
+ end
430
+ end
431
+ end
432
+
433
+ def json_validation_message(obj)
434
+ if obj.respond_to?(:to_str)
435
+ obj = obj.to_str
436
+ elsif obj.respond_to?(:to_json)
437
+ obj = obj.to_json
438
+ end
439
+
440
+ error = nil
441
+ begin
442
+ JSON.parse(obj)
443
+ rescue => e
444
+ error = e
445
+ end
446
+ "contains invalid JSON: #{error}" if error
434
447
  end
435
448
 
436
449
  end
437
450
 
438
- # @overload delete_bucket_tagging(options = {})
451
+ include Validators
452
+ extend Validators
453
+
454
+ end
455
+
456
+ class Client::V20060301 < Client
457
+
458
+ def self.object_method(method_name, verb, *args, &block)
459
+ bucket_method(method_name, verb, *args) do
460
+ configure_request do |req, options|
461
+ validate_key!(options[:key])
462
+ super(req, options)
463
+ req.key = options[:key]
464
+ end
465
+
466
+ instance_eval(&block) if block
467
+ end
468
+ end
469
+
470
+ public
471
+
472
+ # Creates a bucket.
473
+ # @overload create_bucket(options = {})
439
474
  # @param [Hash] options
440
475
  # @option options [required,String] :bucket_name
476
+ # @option options [String] :acl A canned ACL (e.g. 'private',
477
+ # 'public-read', etc). See the S3 API documentation for
478
+ # a complete list of valid values.
479
+ # @option options [String] :grant_read
480
+ # @option options [String] :grant_write
481
+ # @option options [String] :grant_read_acp
482
+ # @option options [String] :grant_write_acp
483
+ # @option options [String] :grant_full_control
441
484
  # @return [Core::Response]
442
- bucket_method(:delete_bucket_tagging, :delete) do
485
+ bucket_method(:create_bucket, :put, :header_options => {
486
+ :acl => 'x-amz-acl',
487
+ :grant_read => 'x-amz-grant-read',
488
+ :grant_write => 'x-amz-grant-write',
489
+ :grant_read_acp => 'x-amz-grant-read-acp',
490
+ :grant_write_acp => 'x-amz-grant-write-acp',
491
+ :grant_full_control => 'x-amz-grant-full-control',
492
+ }) do
493
+
443
494
  configure_request do |req, options|
444
- req.add_param('tagging')
495
+ validate_bucket_name!(options[:bucket_name])
496
+ if location = options[:location_constraint]
497
+ xmlns = "http://s3.amazonaws.com/doc/#{API_VERSION}/"
498
+ req.body = <<-XML
499
+ <CreateBucketConfiguration xmlns="#{xmlns}">
500
+ <LocationConstraint>#{location}</LocationConstraint>
501
+ </CreateBucketConfiguration>
502
+ XML
503
+ end
445
504
  super(req, options)
446
505
  end
506
+
447
507
  end
508
+ alias_method :put_bucket, :create_bucket
448
509
 
449
- # @overload list_buckets(options = {})
510
+ # @!method put_bucket_website(options = {})
450
511
  # @param [Hash] options
512
+ # @option (see WebsiteConfiguration#initialize)
513
+ # @option options [required,String] :bucket_name
451
514
  # @return [Core::Response]
452
- add_client_request_method(:list_buckets) do
515
+ bucket_method(:put_bucket_website, :put, 'website') do
453
516
 
454
517
  configure_request do |req, options|
455
- req.http_method = "GET"
456
- end
518
+ req.body = Nokogiri::XML::Builder.new do |xml|
519
+ xml.WebsiteConfiguration(:xmlns => XMLNS) do
457
520
 
458
- process_response do |resp|
459
- resp.data = XML::ListBuckets.parse(resp.http_response.body)
460
- end
521
+ if redirect = options[:redirect_all_requests_to]
522
+ xml.RedirectAllRequestsTo do
523
+ xml.HostName(redirect[:host_name])
524
+ xml.Protocol(redirect[:protocol]) if redirect[:protocol]
525
+ end
526
+ end
461
527
 
462
- simulate_response do |resp|
463
- resp.data = Core::XML::Parser.new(XML::ListBuckets.rules).simulate
464
- end
528
+ if indx = options[:index_document]
529
+ xml.IndexDocument do
530
+ xml.Suffix(indx[:suffix])
531
+ end
532
+ end
465
533
 
466
- end
534
+ if err = options[:error_document]
535
+ xml.ErrorDocument do
536
+ xml.Key(err[:key])
537
+ end
538
+ end
467
539
 
468
- # Sets the access policy for a bucket.
469
- # @overload set_bucket_policy(options = {})
470
- # @param [Hash] options
471
- # @option options [required,String] :bucket_name
472
- # @option options [required,String] :policy This can be a String
473
- # or any object that responds to `#to_json`.
474
- # @return [Core::Response]
475
- bucket_method(:set_bucket_policy, :put, 'policy') do
540
+ rules = options[:routing_rules]
541
+ if rules.is_a?(Array) && !rules.empty?
542
+ xml.RoutingRules do
543
+ rules.each do |rule|
544
+ xml.RoutingRule do
476
545
 
477
- configure_request do |req, options|
478
- require_policy!(options[:policy])
546
+ redirect = rule[:redirect]
547
+ xml.Redirect do
548
+ xml.Protocol(redirect[:protocol]) if redirect[:protocol]
549
+ xml.HostName(redirect[:host_name]) if redirect[:host_name]
550
+ xml.ReplaceKeyPrefixWith(redirect[:replace_key_prefix_with]) if redirect[:replace_key_prefix_with]
551
+ xml.ReplaceKeyWith(redirect[:replace_key_with]) if redirect[:replace_key_with]
552
+ xml.HttpRedirectCode(redirect[:http_redirect_code]) if redirect[:http_redirect_code]
553
+ end
554
+
555
+ if condition = rule[:condition]
556
+ xml.Condition do
557
+ xml.KeyPrefixEquals(condition[:key_prefix_equals]) if condition[:key_prefix_equals]
558
+ xml.HttpErrorCodeReturnedEquals(condition[:http_error_code_returned_equals]) if condition[:http_error_code_returned_equals]
559
+ end
560
+ end
561
+
562
+ end
563
+ end
564
+ end
565
+ end
566
+
567
+ end
568
+ end.doc.root.to_xml
479
569
  super(req, options)
480
- policy = options[:policy]
481
- policy = policy.to_json unless policy.respond_to?(:to_str)
482
- req.body = policy
483
570
  end
484
571
 
485
572
  end
486
573
 
487
- # Gets the access policy for a bucket.
488
- # @overload get_bucket_policy(options = {})
574
+ # @overload get_bucket_website(options = {})
489
575
  # @param [Hash] options
490
576
  # @option options [required,String] :bucket_name
491
577
  # @return [Core::Response]
492
- bucket_method(:get_bucket_policy, :get, 'policy') do
493
-
494
- process_response do |resp|
495
- resp.data[:policy] = resp.http_response.body
496
- end
578
+ # * `:index_document` - (Hash)
579
+ # * `:suffix` - (String)
580
+ # * `:error_document` - (Hash)
581
+ # * `:key` - (String)
582
+ bucket_method(:get_bucket_website, :get, 'website', XML::GetBucketWebsite)
497
583
 
498
- end
584
+ # @overload delete_bucket_website(options = {})
585
+ # @param [Hash] options
586
+ # @option options [required,String] :bucket_name
587
+ # @return [Core::Response]
588
+ bucket_method(:delete_bucket_website, :delete, 'website')
499
589
 
500
- # Deletes the access policy for a bucket.
501
- # @overload delete_bucket_policy(options = {})
590
+ # Deletes an empty bucket.
591
+ # @overload delete_bucket(options = {})
502
592
  # @param [Hash] options
503
593
  # @option options [required,String] :bucket_name
504
594
  # @return [Core::Response]
505
- bucket_method(:delete_bucket_policy, :delete, 'policy')
595
+ bucket_method(:delete_bucket, :delete)
506
596
 
507
- # @overload set_bucket_versioning(options = {})
597
+ # @overload set_bucket_lifecycle_configuration(options = {})
508
598
  # @param [Hash] options
509
599
  # @option options [required,String] :bucket_name
510
- # @option options [required,String] :state
511
- # @option options [String] :mfa_delete
512
- # @option options [String] :mfa
600
+ # @option options [required,String] :lifecycle_configuration
513
601
  # @return [Core::Response]
514
- bucket_method(:set_bucket_versioning, :put, 'versioning', :header_options => { :mfa => "x-amz-mfa" }) do
602
+ bucket_method(:set_bucket_lifecycle_configuration, :put) do
515
603
 
516
604
  configure_request do |req, options|
517
- state = options[:state].to_s.downcase.capitalize
518
- unless state =~ /^(Enabled|Suspended)$/
519
- raise ArgumentError, "invalid versioning state `#{state}`"
520
- end
605
+ xml = options[:lifecycle_configuration]
606
+ req.add_param('lifecycle')
607
+ req.body = xml
608
+ req.headers['content-md5'] = md5(xml)
609
+ super(req, options)
610
+ end
521
611
 
522
- # Leave validation of MFA options to S3
523
- mfa_delete = options[:mfa_delete].to_s.downcase.capitalize if options[:mfa_delete]
612
+ end
524
613
 
525
- # Generate XML request for versioning
526
- req.body = Nokogiri::XML::Builder.new do |xml|
527
- xml.VersioningConfiguration('xmlns' => XMLNS) do
528
- xml.Status(state)
529
- xml.MfaDelete(mfa_delete) if mfa_delete
530
- end
531
- end.doc.root.to_xml
614
+ # @overload get_bucket_lifecycle_configuration(options = {})
615
+ # @param [Hash] options
616
+ # @option options [required,String] :bucket_name
617
+ # @return [Core::Response]
618
+ bucket_method(:get_bucket_lifecycle_configuration, :get) do
532
619
 
620
+ configure_request do |req, options|
621
+ req.add_param('lifecycle')
533
622
  super(req, options)
534
623
  end
535
624
 
625
+ process_response do |resp|
626
+ xml = resp.http_response.body
627
+ resp.data = XML::GetBucketLifecycleConfiguration.parse(xml)
628
+ end
629
+
536
630
  end
537
631
 
538
- # Gets the bucket's location constraint.
539
- # @overload get_bucket_location(options = {})
632
+ # @overload delete_bucket_lifecycle_configuration(options = {})
540
633
  # @param [Hash] options
541
634
  # @option options [required,String] :bucket_name
542
635
  # @return [Core::Response]
543
- bucket_method(:get_bucket_location, :get, 'location') do
636
+ bucket_method(:delete_bucket_lifecycle_configuration, :delete) do
544
637
 
545
- process_response do |response|
546
- regex = />(.*)<\/LocationConstraint>/
547
- matches = response.http_response.body.to_s.match(regex)
548
- response.data[:location_constraint] = matches ? matches[1] : nil
638
+ configure_request do |req, options|
639
+ req.add_param('lifecycle')
640
+ super(req, options)
549
641
  end
550
642
 
551
643
  end
552
644
 
553
- # @overload put_bucket_logging(options = {})
645
+ # @overload put_bucket_cors(options = {})
554
646
  # @param [Hash] options
555
647
  # @option options [required,String] :bucket_name
556
- # @option options [Boolean] :logging_enabled Set to true if turning on
557
- # bucket logging. If not set or false, all of the following options
558
- # will be ignored.
559
- # @option options [String] :target_bucket The name of the bucket in
560
- # which you want Amazon S3 to store server access logs. You can push
561
- # logs to any bucket you own, including the bucket being logged.
562
- # @option options [String] :target_prefix Allows you to specify a prefix
563
- # for the keys that the log files will be stored under. Recommended
564
- # if you will be writing logs from multiple buckets to the same target
565
- # bucket.
566
- # @option options [Array<Hash>] :grants An array of hashes specifying
567
- # permission grantees. For each hash, specify ONLY ONE of :id, :uri,
568
- # or :email_address.
569
- # * `:email_address` - (String) E-mail address of the person being
570
- # granted logging permissions.
571
- # * `:id` - (String) The canonical user ID of the grantee.
572
- # * `:uri` - (String) URI of the grantee group.
573
- # * `:permission` - (String) Logging permissions given to the Grantee
574
- # for the bucket. The bucket owner is automatically granted FULL_CONTROL
575
- # to all logs delivered to the bucket. This optional element enables
576
- # you grant access to others. Valid Values: FULL_CONTROL | READ | WRITE
648
+ # @option options [required,Array<Hash>] :rules An array of rule hashes.
649
+ # * `:id` - (String) A unique identifier for the rule. The ID
650
+ # value can be up to 255 characters long. The IDs help you find
651
+ # a rule in the configuration.
652
+ # * `:allowed_methods` - (required,Array<String>) A list of HTTP
653
+ # methods that you want to allow the origin to execute.
654
+ # Each rule must identify at least one method.
655
+ # * `:allowed_origins` - (required,Array<String>) A list of origins
656
+ # you want to allow cross-domain requests from. This can
657
+ # contain at most one * wild character.
658
+ # * `:allowed_headers` - (Array<String>) A list of headers allowed
659
+ # in a pre-flight OPTIONS request via the
660
+ # Access-Control-Request-Headers header. Each header name
661
+ # specified in the Access-Control-Request-Headers header must
662
+ # have a corresponding entry in the rule.
663
+ # Amazon S3 will send only the allowed headers in a response
664
+ # that were requested. This can contain at most one * wild
665
+ # character.
666
+ # * `:max_age_seconds` - (Integer) The time in seconds that your
667
+ # browser is to cache the preflight response for the specified
668
+ # resource.
669
+ # * `:expose_headers` - (Array<String>) One or more headers in
670
+ # the response that you want customers to be able to access
671
+ # from their applications (for example, from a JavaScript
672
+ # XMLHttpRequest object).
577
673
  # @return [Core::Response]
578
- bucket_method(:put_bucket_logging, :put) do
674
+ bucket_method(:put_bucket_cors, :put) do
579
675
  configure_request do |req, options|
580
676
 
581
- req.add_param('logging')
677
+ req.add_param('cors')
582
678
 
583
- xml = Nokogiri::XML::Builder.new
584
- xml.BucketLoggingStatus('xmlns' => XMLNS) do |xml|
585
- if options[:logging_enabled] == true
586
- xml.LoggingEnabled do
587
- xml.TargetBucket(options[:target_bucket])
588
- xml.TargetPrefix(options[:target_prefix])
589
- unless options[:grants].nil?
679
+ xml = Nokogiri::XML::Builder.new do |xml|
680
+ xml.CORSConfiguration do
681
+ options[:rules].each do |rule|
682
+ xml.CORSRule do
590
683
 
591
- xml.TargetGrants do
592
- options[:grants].each do |grant|
593
- xml.Grant do
594
- if !grant[:email_address].nil?
595
- xml.Grantee('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
596
- 'xsi:type' => 'AmazonCustomerByEmail') do
597
- xml.EmailAddress(grant[:email_address])
598
- end
599
- elsif !grant[:uri].nil?
600
- xml.Grantee('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
601
- 'xsi:type' => 'Group') do
602
- xml.URI(grant[:uri])
603
- end
604
- elsif !grant[:id].nil?
605
- xml.Grantee('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
606
- 'xsi:type' => 'CanonicalUser') do
607
- xml.ID(grant[:id])
608
- end
609
- end
684
+ xml.ID(rule[:id]) if rule[:id]
610
685
 
611
- xml.Permission(grant[:permission])
612
- end
613
- end
686
+ (rule[:allowed_methods] || []).each do |method|
687
+ xml.AllowedMethod(method)
688
+ end
689
+
690
+ (rule[:allowed_origins] || []).each do |origin|
691
+ xml.AllowedOrigin(origin)
692
+ end
693
+
694
+ (rule[:allowed_headers] || []).each do |header|
695
+ xml.AllowedHeader(header)
696
+ end
697
+
698
+ xml.MaxAgeSeconds(rule[:max_age_seconds]) if
699
+ rule[:max_age_seconds]
700
+
701
+ (rule[:expose_headers] || []).each do |header|
702
+ xml.ExposeHeader(header)
614
703
  end
704
+
615
705
  end
616
706
  end
617
707
  end
618
- end
708
+ end.doc.root.to_xml
619
709
 
620
- xml = xml.doc.root.to_xml
621
710
  req.body = xml
622
711
  req.headers['content-md5'] = md5(xml)
623
-
712
+
624
713
  super(req, options)
625
714
 
626
715
  end
627
716
  end
628
717
 
629
- # Gets the bucket's logging status.
630
- # @overload get_bucket_logging(options = {})
718
+ # @overload get_bucket_cors(options = {})
631
719
  # @param [Hash] options
632
720
  # @option options [required,String] :bucket_name
633
721
  # @return [Core::Response]
634
- bucket_method(:get_bucket_logging, :get, 'logging',
635
- XML::GetBucketLogging)
722
+ bucket_method(:get_bucket_cors, :get) do
636
723
 
637
- # @overload get_bucket_versioning(options = {})
724
+ configure_request do |req, options|
725
+ req.add_param('cors')
726
+ super(req, options)
727
+ end
728
+
729
+ process_response do |resp|
730
+ resp.data = XML::GetBucketCors.parse(resp.http_response.body)
731
+ end
732
+
733
+ end
734
+
735
+ # @overload delete_bucket_cors(options = {})
638
736
  # @param [Hash] options
639
737
  # @option options [required,String] :bucket_name
640
738
  # @return [Core::Response]
641
- bucket_method(:get_bucket_versioning, :get, 'versioning',
642
- XML::GetBucketVersioning)
739
+ bucket_method(:delete_bucket_cors, :delete) do
740
+ configure_request do |req, options|
741
+ req.add_param('cors')
742
+ super(req, options)
743
+ end
744
+ end
643
745
 
644
- # @overload list_object_versions(options = {})
746
+ # @overload put_bucket_tagging(options = {})
645
747
  # @param [Hash] options
646
748
  # @option options [required,String] :bucket_name
647
- # @option options [String] :prefix
648
- # @option options [String] :delimiter
649
- # @option options [String] :max_keys
650
- # @option options [String] :key_marker
651
- # @option options [String] :version_id_marker
749
+ # @option options [Hash] :tags
652
750
  # @return [Core::Response]
653
- bucket_method(:list_object_versions, :get, 'versions',
654
- XML::ListObjectVersions) do
655
-
751
+ bucket_method(:put_bucket_tagging, :put) do
656
752
  configure_request do |req, options|
657
- super(req, options)
658
- params = %w(delimiter key_marker max_keys prefix version_id_marker)
659
- params.each do |param|
660
- if options[param.to_sym]
661
- req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
753
+
754
+ req.add_param('tagging')
755
+
756
+ xml = Nokogiri::XML::Builder.new
757
+ xml.Tagging do |xml|
758
+ xml.TagSet do
759
+ options[:tags].each_pair do |key,value|
760
+ xml.Tag do
761
+ xml.Key(key)
762
+ xml.Value(value)
763
+ end
764
+ end
662
765
  end
663
766
  end
664
- end
665
767
 
768
+ xml = xml.doc.root.to_xml
769
+ req.body = xml
770
+ req.headers['content-md5'] = md5(xml)
771
+
772
+ super(req, options)
773
+
774
+ end
666
775
  end
667
776
 
668
- # Sets the access control list for a bucket. You must specify an ACL
669
- # via one of the following methods:
670
- #
671
- # * as a canned ACL (via `:acl`)
672
- # * as a list of grants (via the `:grant_*` options)
673
- # * as an access control policy document (via `:access_control_policy`)
674
- #
675
- # @example Using a canned acl
676
- # s3_client.put_bucket_acl(
677
- # :bucket_name => 'bucket-name',
678
- # :acl => 'public-read')
679
- #
680
- # @example Using grants
681
- # s3_client.put_bucket_acl(
682
- # :bucket_name => 'bucket-name',
683
- # :grant_read => 'uri="http://acs.amazonaws.com/groups/global/AllUsers"',
684
- # :grant_full_control => 'emailAddress="xyz@amazon.com", id="8a9...fa7"')
685
- #
686
- # @example Using an access control policy document
687
- # policy_xml = <<-XML
688
- # <AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
689
- # <Owner>
690
- # <ID>852b113e7a2f25102679df27bb0ae12b3f85be6BucketOwnerCanonicalUserID</ID>
691
- # <DisplayName>OwnerDisplayName</DisplayName>
692
- # </Owner>
693
- # <AccessControlList>
694
- # <Grant>
695
- # <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser">
696
- # <ID>BucketOwnerCanonicalUserID</ID>
697
- # <DisplayName>OwnerDisplayName</DisplayName>
698
- # </Grantee>
699
- # <Permission>FULL_CONTROL</Permission>
700
- # </Grant>
701
- # <Grant>
702
- # <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Group">
703
- # <URI xmlns="">http://acs.amazonaws.com/groups/global/AllUsers</URI>
704
- # </Grantee>
705
- # <Permission xmlns="">READ</Permission>
706
- # </Grant>
707
- # </AccessControlList>
708
- # </AccessControlPolicy>
709
- #
710
- # XML
711
- # s3_client.put_bucket_acl(
712
- # :bucket_name => 'bucket-name',
713
- # :access_control_policy => policy_xml)
714
- #
715
- # @overload put_bucket_acl(options = {})
777
+ # @overload get_bucket_tagging(options = {})
716
778
  # @param [Hash] options
717
779
  # @option options [required,String] :bucket_name
718
- # @option options [String] :access_control_policy An access control
719
- # policy description as a string of XML. See the S3 API
720
- # documentation for a description.
721
- # @option options [String] :acl A canned ACL (e.g. 'private',
722
- # 'public-read', etc). See the S3 API documentation for
723
- # a complete list of valid values.
724
- # @option options [String] :grant_read
725
- # @option options [String] :grant_write
726
- # @option options [String] :grant_read_acp
727
- # @option options [String] :grant_write_acp
728
- # @option options [String] :grant_full_control
729
780
  # @return [Core::Response]
730
- bucket_method(:put_bucket_acl, :put, 'acl', :header_options => {
731
- :acl => 'x-amz-acl',
732
- :grant_read => 'x-amz-grant-read',
733
- :grant_write => 'x-amz-grant-write',
734
- :grant_read_acp => 'x-amz-grant-read-acp',
735
- :grant_write_acp => 'x-amz-grant-write-acp',
736
- :grant_full_control => 'x-amz-grant-full-control',
737
- }) do
781
+ bucket_method(:get_bucket_tagging, :get) do
738
782
 
739
783
  configure_request do |req, options|
740
- move_access_control_policy(options)
741
- require_acl!(options)
784
+ req.add_param('tagging')
742
785
  super(req, options)
743
- req.body = options[:access_control_policy] if
744
- options[:access_control_policy]
786
+ end
787
+
788
+ process_response do |resp|
789
+ resp.data = XML::GetBucketTagging.parse(resp.http_response.body)
745
790
  end
746
791
 
747
792
  end
748
- alias_method :set_bucket_acl, :put_bucket_acl
749
793
 
750
- # Gets the access control list for a bucket.
751
- # @overload get_bucket_acl(options = {})
794
+ # @overload delete_bucket_tagging(options = {})
752
795
  # @param [Hash] options
753
796
  # @option options [required,String] :bucket_name
754
797
  # @return [Core::Response]
755
- bucket_method(:get_bucket_acl, :get, 'acl', XML::GetBucketAcl)
756
-
757
- # Sets the access control list for an object. You must specify an ACL
758
- # via one of the following methods:
798
+ bucket_method(:delete_bucket_tagging, :delete) do
799
+ configure_request do |req, options|
800
+ req.add_param('tagging')
801
+ super(req, options)
802
+ end
803
+ end
804
+
805
+ # @overload list_buckets(options = {})
806
+ # @param [Hash] options
807
+ # @return [Core::Response]
808
+ add_client_request_method(:list_buckets) do
809
+
810
+ configure_request do |req, options|
811
+ req.http_method = "GET"
812
+ end
813
+
814
+ process_response do |resp|
815
+ resp.data = XML::ListBuckets.parse(resp.http_response.body)
816
+ end
817
+
818
+ simulate_response do |resp|
819
+ resp.data = Core::XML::Parser.new(XML::ListBuckets.rules).simulate
820
+ end
821
+
822
+ end
823
+
824
+ # Sets the access policy for a bucket.
825
+ # @overload set_bucket_policy(options = {})
826
+ # @param [Hash] options
827
+ # @option options [required,String] :bucket_name
828
+ # @option options [required,String] :policy This can be a String
829
+ # or any object that responds to `#to_json`.
830
+ # @return [Core::Response]
831
+ bucket_method(:set_bucket_policy, :put, 'policy') do
832
+
833
+ configure_request do |req, options|
834
+ require_policy!(options[:policy])
835
+ super(req, options)
836
+ policy = options[:policy]
837
+ policy = policy.to_json unless policy.respond_to?(:to_str)
838
+ req.body = policy
839
+ end
840
+
841
+ end
842
+
843
+ # Gets the access policy for a bucket.
844
+ # @overload get_bucket_policy(options = {})
845
+ # @param [Hash] options
846
+ # @option options [required,String] :bucket_name
847
+ # @return [Core::Response]
848
+ bucket_method(:get_bucket_policy, :get, 'policy') do
849
+
850
+ process_response do |resp|
851
+ resp.data[:policy] = resp.http_response.body
852
+ end
853
+
854
+ end
855
+
856
+ # Deletes the access policy for a bucket.
857
+ # @overload delete_bucket_policy(options = {})
858
+ # @param [Hash] options
859
+ # @option options [required,String] :bucket_name
860
+ # @return [Core::Response]
861
+ bucket_method(:delete_bucket_policy, :delete, 'policy')
862
+
863
+ # @overload set_bucket_versioning(options = {})
864
+ # @param [Hash] options
865
+ # @option options [required,String] :bucket_name
866
+ # @option options [required,String] :state
867
+ # @option options [String] :mfa_delete
868
+ # @option options [String] :mfa
869
+ # @return [Core::Response]
870
+ bucket_method(:set_bucket_versioning, :put, 'versioning', :header_options => { :mfa => "x-amz-mfa" }) do
871
+
872
+ configure_request do |req, options|
873
+ state = options[:state].to_s.downcase.capitalize
874
+ unless state =~ /^(Enabled|Suspended)$/
875
+ raise ArgumentError, "invalid versioning state `#{state}`"
876
+ end
877
+
878
+ # Leave validation of MFA options to S3
879
+ mfa_delete = options[:mfa_delete].to_s.downcase.capitalize if options[:mfa_delete]
880
+
881
+ # Generate XML request for versioning
882
+ req.body = Nokogiri::XML::Builder.new do |xml|
883
+ xml.VersioningConfiguration('xmlns' => XMLNS) do
884
+ xml.Status(state)
885
+ xml.MfaDelete(mfa_delete) if mfa_delete
886
+ end
887
+ end.doc.root.to_xml
888
+
889
+ super(req, options)
890
+ end
891
+
892
+ end
893
+
894
+ # Gets the bucket's location constraint.
895
+ # @overload get_bucket_location(options = {})
896
+ # @param [Hash] options
897
+ # @option options [required,String] :bucket_name
898
+ # @return [Core::Response]
899
+ bucket_method(:get_bucket_location, :get, 'location') do
900
+
901
+ process_response do |response|
902
+ regex = />(.*)<\/LocationConstraint>/
903
+ matches = response.http_response.body.to_s.match(regex)
904
+ response.data[:location_constraint] = matches ? matches[1] : nil
905
+ end
906
+
907
+ end
908
+
909
+ # @overload put_bucket_logging(options = {})
910
+ # @param [Hash] options
911
+ # @option options [required,String] :bucket_name
912
+ # @option options [Boolean] :logging_enabled Set to true if turning on
913
+ # bucket logging. If not set or false, all of the following options
914
+ # will be ignored.
915
+ # @option options [String] :target_bucket The name of the bucket in
916
+ # which you want Amazon S3 to store server access logs. You can push
917
+ # logs to any bucket you own, including the bucket being logged.
918
+ # @option options [String] :target_prefix Allows you to specify a prefix
919
+ # for the keys that the log files will be stored under. Recommended
920
+ # if you will be writing logs from multiple buckets to the same target
921
+ # bucket.
922
+ # @option options [Array<Hash>] :grants An array of hashes specifying
923
+ # permission grantees. For each hash, specify ONLY ONE of :id, :uri,
924
+ # or :email_address.
925
+ # * `:email_address` - (String) E-mail address of the person being
926
+ # granted logging permissions.
927
+ # * `:id` - (String) The canonical user ID of the grantee.
928
+ # * `:uri` - (String) URI of the grantee group.
929
+ # * `:permission` - (String) Logging permissions given to the Grantee
930
+ # for the bucket. The bucket owner is automatically granted FULL_CONTROL
931
+ # to all logs delivered to the bucket. This optional element enables
932
+ # you grant access to others. Valid Values: FULL_CONTROL | READ | WRITE
933
+ # @return [Core::Response]
934
+ bucket_method(:put_bucket_logging, :put) do
935
+ configure_request do |req, options|
936
+
937
+ req.add_param('logging')
938
+
939
+ xml = Nokogiri::XML::Builder.new
940
+ xml.BucketLoggingStatus('xmlns' => XMLNS) do |xml|
941
+ if options[:logging_enabled] == true
942
+ xml.LoggingEnabled do
943
+ xml.TargetBucket(options[:target_bucket])
944
+ xml.TargetPrefix(options[:target_prefix])
945
+ unless options[:grants].nil?
946
+
947
+ xml.TargetGrants do
948
+ options[:grants].each do |grant|
949
+ xml.Grant do
950
+ if !grant[:email_address].nil?
951
+ xml.Grantee('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
952
+ 'xsi:type' => 'AmazonCustomerByEmail') do
953
+ xml.EmailAddress(grant[:email_address])
954
+ end
955
+ elsif !grant[:uri].nil?
956
+ xml.Grantee('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
957
+ 'xsi:type' => 'Group') do
958
+ xml.URI(grant[:uri])
959
+ end
960
+ elsif !grant[:id].nil?
961
+ xml.Grantee('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
962
+ 'xsi:type' => 'CanonicalUser') do
963
+ xml.ID(grant[:id])
964
+ end
965
+ end
966
+
967
+ xml.Permission(grant[:permission])
968
+ end
969
+ end
970
+ end
971
+ end
972
+ end
973
+ end
974
+ end
975
+
976
+ xml = xml.doc.root.to_xml
977
+ req.body = xml
978
+ req.headers['content-md5'] = md5(xml)
979
+
980
+ super(req, options)
981
+
982
+ end
983
+ end
984
+
985
+ # Gets the bucket's logging status.
986
+ # @overload get_bucket_logging(options = {})
987
+ # @param [Hash] options
988
+ # @option options [required,String] :bucket_name
989
+ # @return [Core::Response]
990
+ bucket_method(:get_bucket_logging, :get, 'logging',
991
+ XML::GetBucketLogging)
992
+
993
+ # @overload get_bucket_versioning(options = {})
994
+ # @param [Hash] options
995
+ # @option options [required,String] :bucket_name
996
+ # @return [Core::Response]
997
+ bucket_method(:get_bucket_versioning, :get, 'versioning',
998
+ XML::GetBucketVersioning)
999
+
1000
+ # @overload list_object_versions(options = {})
1001
+ # @param [Hash] options
1002
+ # @option options [required,String] :bucket_name
1003
+ # @option options [String] :prefix
1004
+ # @option options [String] :delimiter
1005
+ # @option options [String] :max_keys
1006
+ # @option options [String] :key_marker
1007
+ # @option options [String] :version_id_marker
1008
+ # @return [Core::Response]
1009
+ bucket_method(:list_object_versions, :get, 'versions',
1010
+ XML::ListObjectVersions) do
1011
+
1012
+ configure_request do |req, options|
1013
+ super(req, options)
1014
+ params = %w(delimiter key_marker max_keys prefix version_id_marker)
1015
+ params.each do |param|
1016
+ if options[param.to_sym]
1017
+ req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
1018
+ end
1019
+ end
1020
+ end
1021
+
1022
+ end
1023
+
1024
+ # Sets the access control list for a bucket. You must specify an ACL
1025
+ # via one of the following methods:
759
1026
  #
760
1027
  # * as a canned ACL (via `:acl`)
761
1028
  # * as a list of grants (via the `:grant_*` options)
762
1029
  # * as an access control policy document (via `:access_control_policy`)
763
1030
  #
764
1031
  # @example Using a canned acl
765
- # s3_client.put_object_acl(
1032
+ # s3_client.put_bucket_acl(
766
1033
  # :bucket_name => 'bucket-name',
767
- # :key => 'object-key',
768
1034
  # :acl => 'public-read')
769
1035
  #
770
1036
  # @example Using grants
771
1037
  # s3_client.put_bucket_acl(
772
1038
  # :bucket_name => 'bucket-name',
773
- # :key => 'object-key',
774
1039
  # :grant_read => 'uri="http://acs.amazonaws.com/groups/global/AllUsers"',
775
1040
  # :grant_full_control => 'emailAddress="xyz@amazon.com", id="8a9...fa7"')
776
1041
  #
@@ -801,13 +1066,11 @@ module AWS
801
1066
  # XML
802
1067
  # s3_client.put_bucket_acl(
803
1068
  # :bucket_name => 'bucket-name',
804
- # :key => 'object-key',
805
1069
  # :access_control_policy => policy_xml)
806
1070
  #
807
- # @overload put_object_acl(options = {})
1071
+ # @overload put_bucket_acl(options = {})
808
1072
  # @param [Hash] options
809
1073
  # @option options [required,String] :bucket_name
810
- # @option options [required,String] :key
811
1074
  # @option options [String] :access_control_policy An access control
812
1075
  # policy description as a string of XML. See the S3 API
813
1076
  # documentation for a description.
@@ -820,7 +1083,7 @@ module AWS
820
1083
  # @option options [String] :grant_write_acp
821
1084
  # @option options [String] :grant_full_control
822
1085
  # @return [Core::Response]
823
- object_method(:put_object_acl, :put, 'acl', :header_options => {
1086
+ bucket_method(:put_bucket_acl, :put, 'acl', :header_options => {
824
1087
  :acl => 'x-amz-acl',
825
1088
  :grant_read => 'x-amz-grant-read',
826
1089
  :grant_write => 'x-amz-grant-write',
@@ -838,23 +1101,116 @@ module AWS
838
1101
  end
839
1102
 
840
1103
  end
841
- alias_method :set_object_acl, :put_object_acl
1104
+ alias_method :set_bucket_acl, :put_bucket_acl
842
1105
 
843
- # Gets the access control list for an object.
844
- # @overload get_object_acl(options = {})
1106
+ # Gets the access control list for a bucket.
1107
+ # @overload get_bucket_acl(options = {})
845
1108
  # @param [Hash] options
846
1109
  # @option options [required,String] :bucket_name
847
- # @option options [required,String] :key
848
1110
  # @return [Core::Response]
849
- object_method(:get_object_acl, :get, 'acl', XML::GetBucketAcl)
1111
+ bucket_method(:get_bucket_acl, :get, 'acl', XML::GetBucketAcl)
850
1112
 
851
- # Puts data into an object, replacing the current contents.
852
- #
853
- # s3_client.put_object({
854
- # :bucket_name => 'bucket-name',
855
- # :key => 'readme.txt',
856
- # :data => 'This is the readme for ...',
857
- # })
1113
+ # Sets the access control list for an object. You must specify an ACL
1114
+ # via one of the following methods:
1115
+ #
1116
+ # * as a canned ACL (via `:acl`)
1117
+ # * as a list of grants (via the `:grant_*` options)
1118
+ # * as an access control policy document (via `:access_control_policy`)
1119
+ #
1120
+ # @example Using a canned acl
1121
+ # s3_client.put_object_acl(
1122
+ # :bucket_name => 'bucket-name',
1123
+ # :key => 'object-key',
1124
+ # :acl => 'public-read')
1125
+ #
1126
+ # @example Using grants
1127
+ # s3_client.put_bucket_acl(
1128
+ # :bucket_name => 'bucket-name',
1129
+ # :key => 'object-key',
1130
+ # :grant_read => 'uri="http://acs.amazonaws.com/groups/global/AllUsers"',
1131
+ # :grant_full_control => 'emailAddress="xyz@amazon.com", id="8a9...fa7"')
1132
+ #
1133
+ # @example Using an access control policy document
1134
+ # policy_xml = <<-XML
1135
+ # <AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1136
+ # <Owner>
1137
+ # <ID>852b113e7a2f25102679df27bb0ae12b3f85be6BucketOwnerCanonicalUserID</ID>
1138
+ # <DisplayName>OwnerDisplayName</DisplayName>
1139
+ # </Owner>
1140
+ # <AccessControlList>
1141
+ # <Grant>
1142
+ # <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser">
1143
+ # <ID>BucketOwnerCanonicalUserID</ID>
1144
+ # <DisplayName>OwnerDisplayName</DisplayName>
1145
+ # </Grantee>
1146
+ # <Permission>FULL_CONTROL</Permission>
1147
+ # </Grant>
1148
+ # <Grant>
1149
+ # <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Group">
1150
+ # <URI xmlns="">http://acs.amazonaws.com/groups/global/AllUsers</URI>
1151
+ # </Grantee>
1152
+ # <Permission xmlns="">READ</Permission>
1153
+ # </Grant>
1154
+ # </AccessControlList>
1155
+ # </AccessControlPolicy>
1156
+ #
1157
+ # XML
1158
+ # s3_client.put_bucket_acl(
1159
+ # :bucket_name => 'bucket-name',
1160
+ # :key => 'object-key',
1161
+ # :access_control_policy => policy_xml)
1162
+ #
1163
+ # @overload put_object_acl(options = {})
1164
+ # @param [Hash] options
1165
+ # @option options [required,String] :bucket_name
1166
+ # @option options [required,String] :key
1167
+ # @option options [String] :access_control_policy An access control
1168
+ # policy description as a string of XML. See the S3 API
1169
+ # documentation for a description.
1170
+ # @option options [String] :acl A canned ACL (e.g. 'private',
1171
+ # 'public-read', etc). See the S3 API documentation for
1172
+ # a complete list of valid values.
1173
+ # @option options [String] :grant_read
1174
+ # @option options [String] :grant_write
1175
+ # @option options [String] :grant_read_acp
1176
+ # @option options [String] :grant_write_acp
1177
+ # @option options [String] :grant_full_control
1178
+ # @return [Core::Response]
1179
+ object_method(:put_object_acl, :put, 'acl', :header_options => {
1180
+ :acl => 'x-amz-acl',
1181
+ :grant_read => 'x-amz-grant-read',
1182
+ :grant_write => 'x-amz-grant-write',
1183
+ :grant_read_acp => 'x-amz-grant-read-acp',
1184
+ :grant_write_acp => 'x-amz-grant-write-acp',
1185
+ :grant_full_control => 'x-amz-grant-full-control',
1186
+ }) do
1187
+
1188
+ configure_request do |req, options|
1189
+ move_access_control_policy(options)
1190
+ require_acl!(options)
1191
+ super(req, options)
1192
+ req.body = options[:access_control_policy] if
1193
+ options[:access_control_policy]
1194
+ end
1195
+
1196
+ end
1197
+ alias_method :set_object_acl, :put_object_acl
1198
+
1199
+ # Gets the access control list for an object.
1200
+ # @overload get_object_acl(options = {})
1201
+ # @param [Hash] options
1202
+ # @option options [required,String] :bucket_name
1203
+ # @option options [required,String] :key
1204
+ # @return [Core::Response]
1205
+ object_method(:get_object_acl, :get, 'acl', XML::GetBucketAcl)
1206
+
1207
+ # Puts data into an object, replacing the current contents.
1208
+ #
1209
+ # s3_client.put_object({
1210
+ # :bucket_name => 'bucket-name',
1211
+ # :key => 'readme.txt',
1212
+ # :data => 'This is the readme for ...',
1213
+ # })
858
1214
  #
859
1215
  # @overload put_object(options = {})
860
1216
  # @param [Hash] options
@@ -1270,549 +1626,194 @@ module AWS
1270
1626
 
1271
1627
  end
1272
1628
 
1273
- process_response do |resp|
1274
- extract_object_headers(resp)
1275
- end
1276
-
1277
- simulate_response do |response|
1278
- response.data[:etag] = 'abc123'
1279
- end
1280
- end
1281
-
1282
- # @overload complete_multipart_upload(options = {})
1283
- # @param [Hash] options
1284
- # @option options [required,String] :bucket_name
1285
- # @option options [required,String] :key
1286
- # @option options [required,String] :upload_id
1287
- # @option options [required,Array<Hash>] :parts An array of hashes
1288
- # with the following keys:
1289
- # * `:part_number` [Integer] - *required*
1290
- # * `:etag` [String] - *required*
1291
- # @return [Core::Response]
1292
- object_method(:complete_multipart_upload, :post,
1293
- XML::CompleteMultipartUpload) do
1294
- configure_request do |req, options|
1295
- require_upload_id!(options[:upload_id])
1296
- validate_parts!(options[:parts])
1297
- super(req, options)
1298
- req.add_param('uploadId', options[:upload_id])
1299
-
1300
- req.body = Nokogiri::XML::Builder.new do |xml|
1301
- xml.CompleteMultipartUpload do
1302
- options[:parts].each do |part|
1303
- xml.Part do
1304
- xml.PartNumber(part[:part_number])
1305
- xml.ETag(part[:etag])
1306
- end
1307
- end
1308
- end
1309
- end.doc.root.to_xml
1310
-
1311
- end
1312
-
1313
- process_response do |resp|
1314
- extract_object_headers(resp)
1315
- end
1316
-
1317
- simulate_response do |response|
1318
- response.data = {}
1319
- end
1320
-
1321
- end
1322
-
1323
- # @overload abort_multipart_upload(options = {})
1324
- # @param [Hash] options
1325
- # @option options [required,String] :bucket_name
1326
- # @option options [required,String] :key
1327
- # @option options [required,String] :upload_id
1328
- # @return [Core::Response]
1329
- object_method(:abort_multipart_upload, :delete) do
1330
- configure_request do |req, options|
1331
- require_upload_id!(options[:upload_id])
1332
- super(req, options)
1333
- req.add_param('uploadId', options[:upload_id])
1334
- end
1335
- end
1336
-
1337
- # @overload list_parts(options = {})
1338
- # @param [Hash] options
1339
- # @option options [required,String] :bucket_name
1340
- # @option options [required,String] :key
1341
- # @option options [required,String] :upload_id
1342
- # @option options [Integer] :max_parts
1343
- # @option options [Integer] :part_number_marker
1344
- # @return [Core::Response]
1345
- object_method(:list_parts, :get, XML::ListParts) do
1346
-
1347
- configure_request do |req, options|
1348
- require_upload_id!(options[:upload_id])
1349
- super(req, options)
1350
- req.add_param('uploadId', options[:upload_id])
1351
- req.add_param('max-parts', options[:max_parts])
1352
- req.add_param('part-number-marker', options[:part_number_marker])
1353
- end
1354
-
1355
- end
1356
-
1357
- # Copies an object from one key to another.
1358
- # @overload copy_object(options = {})
1359
- # @param [Hash] options
1360
- # @option options [required, String] :bucket_name Name of the bucket
1361
- # to copy a object into.
1362
- # @option options [required, String] :key Where (object key) in the
1363
- # bucket the object should be copied to.
1364
- # @option options [String] :website_redirect_location If the bucket is
1365
- # configured as a website, redirects requests for this object to
1366
- # another object in the same bucket or to an external URL.
1367
- # @option options [required, String] :copy_source The source
1368
- # bucket name and key, joined by a forward slash ('/').
1369
- # This string must be URL-encoded. Additionally, you must
1370
- # have read access to the source object.
1371
- # @option options [String] :acl A canned ACL (e.g. 'private',
1372
- # 'public-read', etc). See the S3 API documentation for
1373
- # a complete list of valid values.
1374
- # @option options [Symbol,String] :server_side_encryption (nil) The
1375
- # algorithm used to encrypt the object on the server side
1376
- # (e.g. :aes256).
1377
- # @option options [String] :storage_class+ ('STANDARD')
1378
- # Controls whether Reduced Redundancy Storage is enabled for
1379
- # the object. Valid values are 'STANDARD' and
1380
- # 'REDUCED_REDUNDANCY'.
1381
- # @option options [String] :expires The date and time at which the
1382
- # object is no longer cacheable.
1383
- # @option options [String] :grant_read
1384
- # @option options [String] :grant_write
1385
- # @option options [String] :grant_read_acp
1386
- # @option options [String] :grant_write_acp
1387
- # @option options [String] :grant_full_control
1388
- # @return [Core::Response]
1389
- object_method(:copy_object, :put, :header_options => {
1390
- :website_redirect_location => 'x-amz-website-redirect-location',
1391
- :acl => 'x-amz-acl',
1392
- :grant_read => 'x-amz-grant-read',
1393
- :grant_write => 'x-amz-grant-write',
1394
- :grant_read_acp => 'x-amz-grant-read-acp',
1395
- :grant_write_acp => 'x-amz-grant-write-acp',
1396
- :grant_full_control => 'x-amz-grant-full-control',
1397
- :copy_source => 'x-amz-copy-source',
1398
- :cache_control => 'Cache-Control',
1399
- :metadata_directive => 'x-amz-metadata-directive',
1400
- :content_type => 'Content-Type',
1401
- :content_disposition => 'Content-Disposition',
1402
- :expires => 'Expires',
1403
- }) do
1404
-
1405
- configure_request do |req, options|
1406
-
1407
- validate!(:copy_source, options[:copy_source]) do
1408
- "may not be blank" if options[:copy_source].to_s.empty?
1409
- end
1410
-
1411
- options = options.merge(:copy_source => escape_path(options[:copy_source]))
1412
- super(req, options)
1413
- req.metadata = options[:metadata]
1414
- req.storage_class = options[:storage_class]
1415
- req.server_side_encryption = options[:server_side_encryption]
1416
-
1417
- if options[:version_id]
1418
- req.headers['x-amz-copy-source'] += "?versionId=#{options[:version_id]}"
1419
- end
1420
- end
1421
-
1422
- process_response do |resp|
1423
- extract_object_headers(resp)
1424
- end
1425
-
1426
- end
1427
-
1428
- object_method(:copy_part, :put, XML::CopyPart, :header_options => {
1429
- :copy_source => 'x-amz-copy-source',
1430
- :copy_source_range => 'x-amz-copy-source-range',
1431
- }) do
1432
-
1433
- configure_request do |request, options|
1434
-
1435
- validate!(:copy_source, options[:copy_source]) do
1436
- "may not be blank" if options[:copy_source].to_s.empty?
1437
- end
1438
-
1439
- validate!(:copy_source_range, options[:copy_source_range]) do
1440
- "must start with bytes=" if options[:copy_source_range] && !options[:copy_source_range].start_with?("bytes=")
1441
- end
1442
-
1443
- options = options.merge(:copy_source => escape_path(options[:copy_source]))
1444
-
1445
- require_upload_id!(options[:upload_id])
1446
- request.add_param('uploadId', options[:upload_id])
1447
-
1448
- require_part_number!(options[:part_number])
1449
- request.add_param('partNumber', options[:part_number])
1450
-
1451
- super(request, options)
1452
-
1453
- if options[:version_id]
1454
- req.headers['x-amz-copy-source'] += "?versionId=#{options[:version_id]}"
1455
- end
1456
-
1457
- end
1458
-
1459
- end
1460
-
1461
- protected
1462
-
1463
- def extract_error_details response
1464
- if
1465
- (response.http_response.status >= 300 ||
1466
- response.request_type == :complete_multipart_upload) and
1467
- body = response.http_response.body and
1468
- error = Core::XML::Parser.parse(body) and
1469
- error[:code]
1470
- then
1471
- [error[:code], error[:message]]
1472
- end
1473
- end
1474
-
1475
- def empty_response_body? response_body
1476
- response_body.nil? or response_body == ''
1477
- end
1478
-
1479
- # There are a few of s3 requests that can generate empty bodies and
1480
- # yet still be errors. These return empty bodies to comply with the
1481
- # HTTP spec. We have to detect these errors specially.
1482
- def populate_error resp
1483
- code = resp.http_response.status
1484
- if EMPTY_BODY_ERRORS.include?(code) and empty_response_body?(resp.http_response.body)
1485
- error_class = EMPTY_BODY_ERRORS[code]
1486
- resp.error = error_class.new(resp.http_request, resp.http_response)
1487
- else
1488
- super
1489
- end
1490
- end
1491
-
1492
- def retryable_error? response
1493
- super or
1494
- failed_multipart_upload?(response) or
1495
- response.error.is_a?(Errors::RequestTimeout)
1496
- end
1497
-
1498
- # S3 may return a 200 response code in response to complete_multipart_upload
1499
- # and then start streaming whitespace until it knows the final result.
1500
- # At that time it sends an XML message with success or failure.
1501
- def failed_multipart_upload? response
1502
- response.request_type == :complete_multipart_upload &&
1503
- extract_error_details(response)
1504
- end
1505
-
1506
- def new_request
1507
- req = S3::Request.new
1508
- req.force_path_style = config.s3_force_path_style?
1509
- req
1510
- end
1511
-
1512
- # Previously the access control policy could be specified via :acl
1513
- # as a string or an object that responds to #to_xml. The prefered
1514
- # method now is to pass :access_control_policy an xml document.
1515
- def move_access_control_policy options
1516
- if acl = options[:acl]
1517
- if acl.is_a?(String) and is_xml?(acl)
1518
- options[:access_control_policy] = options.delete(:acl)
1519
- elsif acl.respond_to?(:to_xml)
1520
- options[:access_control_policy] = options.delete(:acl).to_xml
1521
- end
1522
- end
1523
- end
1524
-
1525
- # @param [String] possible_xml
1526
- # @return [Boolean] Returns `true` if the given string is a valid xml
1527
- # document.
1528
- def is_xml? possible_xml
1529
- begin
1530
- REXML::Document.new(possible_xml).has_elements?
1531
- rescue
1532
- false
1533
- end
1534
- end
1535
-
1536
- def md5 str
1537
- Base64.encode64(Digest::MD5.digest(str)).strip
1538
- end
1539
-
1540
- def parse_copy_part_response resp
1541
- doc = REXML::Document.new(resp.http_response.body)
1542
- resp[:etag] = doc.root.elements["ETag"].text
1543
- resp[:last_modified] = doc.root.elements["LastModified"].text
1544
- if header = resp.http_response.headers['x-amzn-requestid']
1545
- data[:request_id] = [header].flatten.first
1546
- end
1547
- end
1548
-
1549
- def extract_object_headers resp
1550
- meta = {}
1551
- resp.http_response.headers.each_pair do |name,value|
1552
- if name =~ /^x-amz-meta-(.+)$/i
1553
- meta[$1] = [value].flatten.join
1554
- end
1555
- end
1556
- resp.data[:meta] = meta
1557
-
1558
- if expiry = resp.http_response.headers['x-amz-expiration']
1559
- expiry.first =~ /^expiry-date="(.+)", rule-id="(.+)"$/
1560
- exp_date = DateTime.parse($1)
1561
- exp_rule_id = $2
1562
- else
1563
- exp_date = nil
1564
- exp_rule_id = nil
1565
- end
1566
- resp.data[:expiration_date] = exp_date if exp_date
1567
- resp.data[:expiration_rule_id] = exp_rule_id if exp_rule_id
1568
-
1569
- restoring = false
1570
- restore_date = nil
1571
-
1572
- if restore = resp.http_response.headers['x-amz-restore']
1573
- if restore.first =~ /ongoing-request="(.+?)", expiry-date="(.+?)"/
1574
- restoring = $1 == "true"
1575
- restore_date = $2 && DateTime.parse($2)
1576
- elsif restore.first =~ /ongoing-request="(.+?)"/
1577
- restoring = $1 == "true"
1578
- end
1579
- end
1580
- resp.data[:restore_in_progress] = restoring
1581
- resp.data[:restore_expiration_date] = restore_date if restore_date
1582
-
1583
- {
1584
- 'x-amz-version-id' => :version_id,
1585
- 'content-type' => :content_type,
1586
- 'content-encoding' => :content_encoding,
1587
- 'cache-control' => :cache_control,
1588
- 'expires' => :expires,
1589
- 'etag' => :etag,
1590
- 'x-amz-website-redirect-location' => :website_redirect_location,
1591
- 'accept-ranges' => :accept_ranges,
1592
- }.each_pair do |header,method|
1593
- if value = resp.http_response.header(header)
1594
- resp.data[method] = value
1595
- end
1596
- end
1597
-
1598
- if time = resp.http_response.header('Last-Modified')
1599
- resp.data[:last_modified] = Time.parse(time)
1600
- end
1601
-
1602
- if length = resp.http_response.header('content-length')
1603
- resp.data[:content_length] = length.to_i
1604
- end
1605
-
1606
- if sse = resp.http_response.header('x-amz-server-side-encryption')
1607
- resp.data[:server_side_encryption] = sse.downcase.to_sym
1608
- end
1609
-
1610
- end
1611
-
1612
- module Validators
1613
-
1614
- # @return [Boolean] Returns true if the given bucket name is valid.
1615
- def valid_bucket_name?(bucket_name)
1616
- validate_bucket_name!(bucket_name) rescue false
1617
- end
1618
-
1619
- # Returns true if the given `bucket_name` is DNS compatible.
1620
- #
1621
- # DNS compatible bucket names may be accessed like:
1622
- #
1623
- # http://dns.compat.bucket.name.s3.amazonaws.com/
1624
- #
1625
- # Whereas non-dns compatible bucket names must place the bucket
1626
- # name in the url path, like:
1627
- #
1628
- # http://s3.amazonaws.com/dns_incompat_bucket_name/
1629
- #
1630
- # @return [Boolean] Returns true if the given bucket name may be
1631
- # is dns compatible.
1632
- # this bucket n
1633
- #
1634
- def dns_compatible_bucket_name?(bucket_name)
1635
- return false if
1636
- !valid_bucket_name?(bucket_name) or
1637
-
1638
- # Bucket names should be between 3 and 63 characters long
1639
- bucket_name.size > 63 or
1640
-
1641
- # Bucket names must only contain lowercase letters, numbers, dots, and dashes
1642
- # and must start and end with a lowercase letter or a number
1643
- bucket_name !~ /^[a-z0-9][a-z0-9.-]+[a-z0-9]$/ or
1644
-
1645
- # Bucket names should not be formatted like an IP address (e.g., 192.168.5.4)
1646
- bucket_name =~ /(\d+\.){3}\d+/ or
1647
-
1648
- # Bucket names cannot contain two, adjacent periods
1649
- bucket_name['..'] or
1650
-
1651
- # Bucket names cannot contain dashes next to periods
1652
- # (e.g., "my-.bucket.com" and "my.-bucket" are invalid)
1653
- (bucket_name['-.'] || bucket_name['.-'])
1654
-
1655
- true
1656
- end
1657
-
1658
- # Returns true if the bucket name must be used in the request
1659
- # path instead of as a sub-domain when making requests against
1660
- # S3.
1661
- #
1662
- # This can be an issue if the bucket name is DNS compatible but
1663
- # contains '.' (periods). These cause the SSL certificate to
1664
- # become invalid when making authenticated requets over SSL to the
1665
- # bucket name. The solution is to send this as a path argument
1666
- # instead.
1667
- #
1668
- # @return [Boolean] Returns true if the bucket name should be used
1669
- # as a path segement instead of dns prefix when making requests
1670
- # against s3.
1671
- #
1672
- def path_style_bucket_name? bucket_name
1673
- if dns_compatible_bucket_name?(bucket_name)
1674
- bucket_name =~ /\./ ? true : false
1675
- else
1676
- true
1677
- end
1678
- end
1679
-
1680
- def validate! name, value, &block
1681
- if error_msg = yield
1682
- raise ArgumentError, "#{name} #{error_msg}"
1683
- end
1684
- value
1685
- end
1686
-
1687
- def validate_key!(key)
1688
- validate!('key', key) do
1689
- case
1690
- when key.nil? || key == ''
1691
- 'may not be blank'
1692
- end
1693
- end
1629
+ process_response do |resp|
1630
+ extract_object_headers(resp)
1694
1631
  end
1695
1632
 
1696
- def require_bucket_name! bucket_name
1697
- if [nil, ''].include?(bucket_name)
1698
- raise ArgumentError, "bucket_name may not be blank"
1699
- end
1633
+ simulate_response do |response|
1634
+ response.data[:etag] = 'abc123'
1700
1635
  end
1636
+ end
1701
1637
 
1702
- # Returns true if the given bucket name is valid. If the name
1703
- # is invalid, an ArgumentError is raised.
1704
- def validate_bucket_name!(bucket_name)
1705
- validate!('bucket_name', bucket_name) do
1706
- case
1707
- when bucket_name.nil? || bucket_name == ''
1708
- 'may not be blank'
1709
- when bucket_name !~ /^[A-Za-z0-9._\-]+$/
1710
- 'may only contain uppercase letters, lowercase letters, numbers, periods (.), ' +
1711
- 'underscores (_), and dashes (-)'
1712
- when !(3..255).include?(bucket_name.size)
1713
- 'must be between 3 and 255 characters long'
1714
- when bucket_name =~ /\n/
1715
- 'must not contain a newline character'
1638
+ # @overload complete_multipart_upload(options = {})
1639
+ # @param [Hash] options
1640
+ # @option options [required,String] :bucket_name
1641
+ # @option options [required,String] :key
1642
+ # @option options [required,String] :upload_id
1643
+ # @option options [required,Array<Hash>] :parts An array of hashes
1644
+ # with the following keys:
1645
+ # * `:part_number` [Integer] - *required*
1646
+ # * `:etag` [String] - *required*
1647
+ # @return [Core::Response]
1648
+ object_method(:complete_multipart_upload, :post,
1649
+ XML::CompleteMultipartUpload) do
1650
+ configure_request do |req, options|
1651
+ require_upload_id!(options[:upload_id])
1652
+ validate_parts!(options[:parts])
1653
+ super(req, options)
1654
+ req.add_param('uploadId', options[:upload_id])
1655
+
1656
+ req.body = Nokogiri::XML::Builder.new do |xml|
1657
+ xml.CompleteMultipartUpload do
1658
+ options[:parts].each do |part|
1659
+ xml.Part do
1660
+ xml.PartNumber(part[:part_number])
1661
+ xml.ETag(part[:etag])
1662
+ end
1663
+ end
1716
1664
  end
1717
- end
1665
+ end.doc.root.to_xml
1666
+
1718
1667
  end
1719
1668
 
1720
- def require_policy!(policy)
1721
- validate!('policy', policy) do
1722
- case
1723
- when policy.nil? || policy == ''
1724
- 'may not be blank'
1725
- else
1726
- json_validation_message(policy)
1727
- end
1728
- end
1669
+ process_response do |resp|
1670
+ extract_object_headers(resp)
1729
1671
  end
1730
1672
 
1731
- def require_acl! options
1732
- acl_options = [
1733
- :acl,
1734
- :grant_read,
1735
- :grant_write,
1736
- :grant_read_acp,
1737
- :grant_write_acp,
1738
- :grant_full_control,
1739
- :access_control_policy,
1740
- ]
1741
- unless options.keys.any?{|opt| acl_options.include?(opt) }
1742
- msg = "missing a required ACL option, must provide an ACL " +
1743
- "via :acl, :grant_* or :access_control_policy"
1744
- raise ArgumentError, msg
1745
- end
1673
+ simulate_response do |response|
1674
+ response.data = {}
1746
1675
  end
1747
1676
 
1748
- def set_body_stream_and_content_length request, options
1677
+ end
1749
1678
 
1750
- unless options[:content_length]
1751
- msg = "S3 requires a content-length header, unable to determine "
1752
- msg << "the content length of the data provided, please set "
1753
- msg << ":content_length"
1754
- raise ArgumentError, msg
1755
- end
1679
+ # @overload abort_multipart_upload(options = {})
1680
+ # @param [Hash] options
1681
+ # @option options [required,String] :bucket_name
1682
+ # @option options [required,String] :key
1683
+ # @option options [required,String] :upload_id
1684
+ # @return [Core::Response]
1685
+ object_method(:abort_multipart_upload, :delete) do
1686
+ configure_request do |req, options|
1687
+ require_upload_id!(options[:upload_id])
1688
+ super(req, options)
1689
+ req.add_param('uploadId', options[:upload_id])
1690
+ end
1691
+ end
1756
1692
 
1757
- request.headers['content-length'] = options[:content_length]
1758
- request.body_stream = options[:data]
1693
+ # @overload list_parts(options = {})
1694
+ # @param [Hash] options
1695
+ # @option options [required,String] :bucket_name
1696
+ # @option options [required,String] :key
1697
+ # @option options [required,String] :upload_id
1698
+ # @option options [Integer] :max_parts
1699
+ # @option options [Integer] :part_number_marker
1700
+ # @return [Core::Response]
1701
+ object_method(:list_parts, :get, XML::ListParts) do
1759
1702
 
1703
+ configure_request do |req, options|
1704
+ require_upload_id!(options[:upload_id])
1705
+ super(req, options)
1706
+ req.add_param('uploadId', options[:upload_id])
1707
+ req.add_param('max-parts', options[:max_parts])
1708
+ req.add_param('part-number-marker', options[:part_number_marker])
1760
1709
  end
1761
1710
 
1762
- def require_upload_id!(upload_id)
1763
- validate!("upload_id", upload_id) do
1764
- "must not be blank" if upload_id.to_s.empty?
1711
+ end
1712
+
1713
+ # Copies an object from one key to another.
1714
+ # @overload copy_object(options = {})
1715
+ # @param [Hash] options
1716
+ # @option options [required, String] :bucket_name Name of the bucket
1717
+ # to copy a object into.
1718
+ # @option options [required, String] :key Where (object key) in the
1719
+ # bucket the object should be copied to.
1720
+ # @option options [String] :website_redirect_location If the bucket is
1721
+ # configured as a website, redirects requests for this object to
1722
+ # another object in the same bucket or to an external URL.
1723
+ # @option options [required, String] :copy_source The source
1724
+ # bucket name and key, joined by a forward slash ('/').
1725
+ # This string must be URL-encoded. Additionally, you must
1726
+ # have read access to the source object.
1727
+ # @option options [String] :acl A canned ACL (e.g. 'private',
1728
+ # 'public-read', etc). See the S3 API documentation for
1729
+ # a complete list of valid values.
1730
+ # @option options [Symbol,String] :server_side_encryption (nil) The
1731
+ # algorithm used to encrypt the object on the server side
1732
+ # (e.g. :aes256).
1733
+ # @option options [String] :storage_class+ ('STANDARD')
1734
+ # Controls whether Reduced Redundancy Storage is enabled for
1735
+ # the object. Valid values are 'STANDARD' and
1736
+ # 'REDUCED_REDUNDANCY'.
1737
+ # @option options [String] :expires The date and time at which the
1738
+ # object is no longer cacheable.
1739
+ # @option options [String] :grant_read
1740
+ # @option options [String] :grant_write
1741
+ # @option options [String] :grant_read_acp
1742
+ # @option options [String] :grant_write_acp
1743
+ # @option options [String] :grant_full_control
1744
+ # @return [Core::Response]
1745
+ object_method(:copy_object, :put, :header_options => {
1746
+ :website_redirect_location => 'x-amz-website-redirect-location',
1747
+ :acl => 'x-amz-acl',
1748
+ :grant_read => 'x-amz-grant-read',
1749
+ :grant_write => 'x-amz-grant-write',
1750
+ :grant_read_acp => 'x-amz-grant-read-acp',
1751
+ :grant_write_acp => 'x-amz-grant-write-acp',
1752
+ :grant_full_control => 'x-amz-grant-full-control',
1753
+ :copy_source => 'x-amz-copy-source',
1754
+ :cache_control => 'Cache-Control',
1755
+ :metadata_directive => 'x-amz-metadata-directive',
1756
+ :content_type => 'Content-Type',
1757
+ :content_disposition => 'Content-Disposition',
1758
+ :expires => 'Expires',
1759
+ }) do
1760
+
1761
+ configure_request do |req, options|
1762
+
1763
+ validate!(:copy_source, options[:copy_source]) do
1764
+ "may not be blank" if options[:copy_source].to_s.empty?
1765
1765
  end
1766
- end
1767
1766
 
1768
- def require_part_number! part_number
1769
- validate!("part_number", part_number) do
1770
- "must not be blank" if part_number.to_s.empty?
1767
+ options = options.merge(:copy_source => escape_path(options[:copy_source]))
1768
+ super(req, options)
1769
+ req.metadata = options[:metadata]
1770
+ req.storage_class = options[:storage_class]
1771
+ req.server_side_encryption = options[:server_side_encryption]
1772
+
1773
+ if options[:version_id]
1774
+ req.headers['x-amz-copy-source'] += "?versionId=#{options[:version_id]}"
1771
1775
  end
1772
1776
  end
1773
1777
 
1774
- def validate_parts!(parts)
1775
- validate!("parts", parts) do
1776
- if !parts.kind_of?(Array)
1777
- "must not be blank"
1778
- elsif parts.empty?
1779
- "must contain at least one entry"
1780
- elsif !parts.all? { |p| p.kind_of?(Hash) }
1781
- "must be an array of hashes"
1782
- elsif !parts.all? { |p| p[:part_number] }
1783
- "must contain part_number for each part"
1784
- elsif !parts.all? { |p| p[:etag] }
1785
- "must contain etag for each part"
1786
- elsif parts.any? { |p| p[:part_number].to_i < 1 }
1787
- "must not have part numbers less than 1"
1788
- end
1789
- end
1778
+ process_response do |resp|
1779
+ extract_object_headers(resp)
1790
1780
  end
1791
1781
 
1792
- def json_validation_message(obj)
1793
- if obj.respond_to?(:to_str)
1794
- obj = obj.to_str
1795
- elsif obj.respond_to?(:to_json)
1796
- obj = obj.to_json
1782
+ end
1783
+
1784
+ object_method(:copy_part, :put, XML::CopyPart, :header_options => {
1785
+ :copy_source => 'x-amz-copy-source',
1786
+ :copy_source_range => 'x-amz-copy-source-range',
1787
+ }) do
1788
+
1789
+ configure_request do |request, options|
1790
+
1791
+ validate!(:copy_source, options[:copy_source]) do
1792
+ "may not be blank" if options[:copy_source].to_s.empty?
1797
1793
  end
1798
1794
 
1799
- error = nil
1800
- begin
1801
- JSON.parse(obj)
1802
- rescue => e
1803
- error = e
1795
+ validate!(:copy_source_range, options[:copy_source_range]) do
1796
+ "must start with bytes=" if options[:copy_source_range] && !options[:copy_source_range].start_with?("bytes=")
1804
1797
  end
1805
- "contains invalid JSON: #{error}" if error
1806
- end
1807
1798
 
1808
- end
1799
+ options = options.merge(:copy_source => escape_path(options[:copy_source]))
1809
1800
 
1810
- include Validators
1811
- extend Validators
1801
+ require_upload_id!(options[:upload_id])
1802
+ request.add_param('uploadId', options[:upload_id])
1812
1803
 
1813
- end
1804
+ require_part_number!(options[:part_number])
1805
+ request.add_param('partNumber', options[:part_number])
1806
+
1807
+ super(request, options)
1814
1808
 
1815
- class Client::V20060301 < Client; end
1809
+ if options[:version_id]
1810
+ req.headers['x-amz-copy-source'] += "?versionId=#{options[:version_id]}"
1811
+ end
1812
+
1813
+ end
1814
+
1815
+ end
1816
1816
 
1817
+ end
1817
1818
  end
1818
1819
  end