aws-sdk 1.17.0 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
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