aws-sdk 1.0.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.
- data/.yardopts +6 -0
- data/LICENSE.txt +171 -0
- data/NOTICE.txt +2 -0
- data/README.rdoc +189 -0
- data/lib/aws-sdk.rb +14 -0
- data/lib/aws.rb +63 -0
- data/lib/aws/api_config.rb +45 -0
- data/lib/aws/api_config/.document +0 -0
- data/lib/aws/api_config/EC2-2011-02-28.yml +2314 -0
- data/lib/aws/api_config/SNS-2010-03-31.yml +171 -0
- data/lib/aws/api_config/SQS-2009-02-01.yml +161 -0
- data/lib/aws/api_config/SimpleDB-2009-04-15.yml +278 -0
- data/lib/aws/api_config/SimpleEmailService-2010-12-01.yml +147 -0
- data/lib/aws/api_config_transform.rb +32 -0
- data/lib/aws/async_handle.rb +90 -0
- data/lib/aws/authorize_v2.rb +37 -0
- data/lib/aws/authorize_v3.rb +37 -0
- data/lib/aws/base_client.rb +524 -0
- data/lib/aws/cacheable.rb +92 -0
- data/lib/aws/common.rb +228 -0
- data/lib/aws/configurable.rb +36 -0
- data/lib/aws/configuration.rb +272 -0
- data/lib/aws/configured_client_methods.rb +81 -0
- data/lib/aws/configured_grammars.rb +65 -0
- data/lib/aws/configured_option_grammars.rb +46 -0
- data/lib/aws/configured_xml_grammars.rb +47 -0
- data/lib/aws/default_signer.rb +38 -0
- data/lib/aws/ec2.rb +321 -0
- data/lib/aws/ec2/attachment.rb +149 -0
- data/lib/aws/ec2/attachment_collection.rb +57 -0
- data/lib/aws/ec2/availability_zone.rb +80 -0
- data/lib/aws/ec2/availability_zone_collection.rb +47 -0
- data/lib/aws/ec2/block_device_mappings.rb +53 -0
- data/lib/aws/ec2/client.rb +54 -0
- data/lib/aws/ec2/client/xml.rb +127 -0
- data/lib/aws/ec2/collection.rb +39 -0
- data/lib/aws/ec2/config_transform.rb +63 -0
- data/lib/aws/ec2/elastic_ip.rb +107 -0
- data/lib/aws/ec2/elastic_ip_collection.rb +85 -0
- data/lib/aws/ec2/errors.rb +29 -0
- data/lib/aws/ec2/filtered_collection.rb +65 -0
- data/lib/aws/ec2/has_permissions.rb +46 -0
- data/lib/aws/ec2/image.rb +245 -0
- data/lib/aws/ec2/image_collection.rb +235 -0
- data/lib/aws/ec2/instance.rb +515 -0
- data/lib/aws/ec2/instance_collection.rb +276 -0
- data/lib/aws/ec2/key_pair.rb +86 -0
- data/lib/aws/ec2/key_pair_collection.rb +102 -0
- data/lib/aws/ec2/permission_collection.rb +177 -0
- data/lib/aws/ec2/region.rb +81 -0
- data/lib/aws/ec2/region_collection.rb +55 -0
- data/lib/aws/ec2/request.rb +27 -0
- data/lib/aws/ec2/reserved_instances.rb +50 -0
- data/lib/aws/ec2/reserved_instances_collection.rb +44 -0
- data/lib/aws/ec2/reserved_instances_offering.rb +55 -0
- data/lib/aws/ec2/reserved_instances_offering_collection.rb +43 -0
- data/lib/aws/ec2/resource.rb +340 -0
- data/lib/aws/ec2/resource_tag_collection.rb +218 -0
- data/lib/aws/ec2/security_group.rb +246 -0
- data/lib/aws/ec2/security_group/ip_permission.rb +70 -0
- data/lib/aws/ec2/security_group/ip_permission_collection.rb +59 -0
- data/lib/aws/ec2/security_group_collection.rb +132 -0
- data/lib/aws/ec2/snapshot.rb +138 -0
- data/lib/aws/ec2/snapshot_collection.rb +90 -0
- data/lib/aws/ec2/tag.rb +88 -0
- data/lib/aws/ec2/tag_collection.rb +114 -0
- data/lib/aws/ec2/tagged_collection.rb +48 -0
- data/lib/aws/ec2/tagged_item.rb +87 -0
- data/lib/aws/ec2/volume.rb +190 -0
- data/lib/aws/ec2/volume_collection.rb +95 -0
- data/lib/aws/errors.rb +129 -0
- data/lib/aws/http/builtin_handler.rb +69 -0
- data/lib/aws/http/curb_handler.rb +123 -0
- data/lib/aws/http/handler.rb +77 -0
- data/lib/aws/http/httparty_handler.rb +61 -0
- data/lib/aws/http/request.rb +136 -0
- data/lib/aws/http/request_param.rb +63 -0
- data/lib/aws/http/response.rb +75 -0
- data/lib/aws/ignore_result_element.rb +38 -0
- data/lib/aws/indifferent_hash.rb +86 -0
- data/lib/aws/inflection.rb +46 -0
- data/lib/aws/lazy_error_classes.rb +64 -0
- data/lib/aws/meta_utils.rb +43 -0
- data/lib/aws/model.rb +57 -0
- data/lib/aws/naming.rb +32 -0
- data/lib/aws/option_grammar.rb +544 -0
- data/lib/aws/policy.rb +912 -0
- data/lib/aws/rails.rb +209 -0
- data/lib/aws/record.rb +79 -0
- data/lib/aws/record/attribute.rb +94 -0
- data/lib/aws/record/attribute_macros.rb +288 -0
- data/lib/aws/record/attributes/boolean.rb +49 -0
- data/lib/aws/record/attributes/datetime.rb +86 -0
- data/lib/aws/record/attributes/float.rb +48 -0
- data/lib/aws/record/attributes/integer.rb +68 -0
- data/lib/aws/record/attributes/sortable_float.rb +60 -0
- data/lib/aws/record/attributes/sortable_integer.rb +95 -0
- data/lib/aws/record/attributes/string.rb +69 -0
- data/lib/aws/record/base.rb +728 -0
- data/lib/aws/record/conversion.rb +38 -0
- data/lib/aws/record/dirty_tracking.rb +286 -0
- data/lib/aws/record/errors.rb +153 -0
- data/lib/aws/record/exceptions.rb +48 -0
- data/lib/aws/record/finder_methods.rb +262 -0
- data/lib/aws/record/naming.rb +31 -0
- data/lib/aws/record/scope.rb +157 -0
- data/lib/aws/record/validations.rb +653 -0
- data/lib/aws/record/validator.rb +237 -0
- data/lib/aws/record/validators/acceptance.rb +51 -0
- data/lib/aws/record/validators/block.rb +38 -0
- data/lib/aws/record/validators/confirmation.rb +43 -0
- data/lib/aws/record/validators/count.rb +108 -0
- data/lib/aws/record/validators/exclusion.rb +43 -0
- data/lib/aws/record/validators/format.rb +57 -0
- data/lib/aws/record/validators/inclusion.rb +56 -0
- data/lib/aws/record/validators/length.rb +107 -0
- data/lib/aws/record/validators/numericality.rb +138 -0
- data/lib/aws/record/validators/presence.rb +45 -0
- data/lib/aws/resource_cache.rb +39 -0
- data/lib/aws/response.rb +113 -0
- data/lib/aws/response_cache.rb +50 -0
- data/lib/aws/s3.rb +109 -0
- data/lib/aws/s3/access_control_list.rb +252 -0
- data/lib/aws/s3/acl_object.rb +266 -0
- data/lib/aws/s3/bucket.rb +320 -0
- data/lib/aws/s3/bucket_collection.rb +122 -0
- data/lib/aws/s3/bucket_version_collection.rb +85 -0
- data/lib/aws/s3/client.rb +999 -0
- data/lib/aws/s3/client/xml.rb +190 -0
- data/lib/aws/s3/data_options.rb +99 -0
- data/lib/aws/s3/errors.rb +43 -0
- data/lib/aws/s3/multipart_upload.rb +318 -0
- data/lib/aws/s3/multipart_upload_collection.rb +78 -0
- data/lib/aws/s3/object_collection.rb +159 -0
- data/lib/aws/s3/object_metadata.rb +67 -0
- data/lib/aws/s3/object_upload_collection.rb +83 -0
- data/lib/aws/s3/object_version.rb +141 -0
- data/lib/aws/s3/object_version_collection.rb +78 -0
- data/lib/aws/s3/paginated_collection.rb +94 -0
- data/lib/aws/s3/policy.rb +76 -0
- data/lib/aws/s3/prefix_and_delimiter_collection.rb +56 -0
- data/lib/aws/s3/prefixed_collection.rb +84 -0
- data/lib/aws/s3/presigned_post.rb +504 -0
- data/lib/aws/s3/request.rb +198 -0
- data/lib/aws/s3/s3_object.rb +794 -0
- data/lib/aws/s3/tree.rb +116 -0
- data/lib/aws/s3/tree/branch_node.rb +71 -0
- data/lib/aws/s3/tree/child_collection.rb +108 -0
- data/lib/aws/s3/tree/leaf_node.rb +99 -0
- data/lib/aws/s3/tree/node.rb +22 -0
- data/lib/aws/s3/tree/parent.rb +90 -0
- data/lib/aws/s3/uploaded_part.rb +82 -0
- data/lib/aws/s3/uploaded_part_collection.rb +86 -0
- data/lib/aws/service_interface.rb +60 -0
- data/lib/aws/simple_db.rb +202 -0
- data/lib/aws/simple_db/attribute.rb +159 -0
- data/lib/aws/simple_db/attribute_collection.rb +227 -0
- data/lib/aws/simple_db/client.rb +52 -0
- data/lib/aws/simple_db/client/options.rb +34 -0
- data/lib/aws/simple_db/client/xml.rb +68 -0
- data/lib/aws/simple_db/consistent_read_option.rb +42 -0
- data/lib/aws/simple_db/delete_attributes.rb +64 -0
- data/lib/aws/simple_db/domain.rb +118 -0
- data/lib/aws/simple_db/domain_collection.rb +116 -0
- data/lib/aws/simple_db/domain_metadata.rb +112 -0
- data/lib/aws/simple_db/errors.rb +46 -0
- data/lib/aws/simple_db/expect_condition_option.rb +45 -0
- data/lib/aws/simple_db/item.rb +84 -0
- data/lib/aws/simple_db/item_collection.rb +594 -0
- data/lib/aws/simple_db/item_data.rb +70 -0
- data/lib/aws/simple_db/put_attributes.rb +62 -0
- data/lib/aws/simple_db/request.rb +27 -0
- data/lib/aws/simple_email_service.rb +373 -0
- data/lib/aws/simple_email_service/client.rb +39 -0
- data/lib/aws/simple_email_service/client/options.rb +24 -0
- data/lib/aws/simple_email_service/client/xml.rb +38 -0
- data/lib/aws/simple_email_service/email_address_collection.rb +66 -0
- data/lib/aws/simple_email_service/errors.rb +29 -0
- data/lib/aws/simple_email_service/quotas.rb +64 -0
- data/lib/aws/simple_email_service/request.rb +27 -0
- data/lib/aws/sns.rb +69 -0
- data/lib/aws/sns/client.rb +37 -0
- data/lib/aws/sns/client/options.rb +24 -0
- data/lib/aws/sns/client/xml.rb +38 -0
- data/lib/aws/sns/errors.rb +29 -0
- data/lib/aws/sns/policy.rb +49 -0
- data/lib/aws/sns/request.rb +27 -0
- data/lib/aws/sns/subscription.rb +100 -0
- data/lib/aws/sns/subscription_collection.rb +84 -0
- data/lib/aws/sns/topic.rb +384 -0
- data/lib/aws/sns/topic_collection.rb +70 -0
- data/lib/aws/sns/topic_subscription_collection.rb +58 -0
- data/lib/aws/sqs.rb +70 -0
- data/lib/aws/sqs/client.rb +38 -0
- data/lib/aws/sqs/client/xml.rb +36 -0
- data/lib/aws/sqs/errors.rb +33 -0
- data/lib/aws/sqs/policy.rb +50 -0
- data/lib/aws/sqs/queue.rb +507 -0
- data/lib/aws/sqs/queue_collection.rb +105 -0
- data/lib/aws/sqs/received_message.rb +184 -0
- data/lib/aws/sqs/received_sns_message.rb +112 -0
- data/lib/aws/sqs/request.rb +44 -0
- data/lib/aws/xml_grammar.rb +923 -0
- data/rails/init.rb +15 -0
- metadata +298 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
|
|
14
|
+
require 'aws/model'
|
|
15
|
+
require 'aws/s3/bucket'
|
|
16
|
+
|
|
17
|
+
module AWS
|
|
18
|
+
class S3
|
|
19
|
+
|
|
20
|
+
# Represents a collection of buckets.
|
|
21
|
+
#
|
|
22
|
+
# You can use this to create a bucket:
|
|
23
|
+
#
|
|
24
|
+
# s3.buckets.create(:name => "mybucket")
|
|
25
|
+
#
|
|
26
|
+
# You can get a handle for a specific bucket with indifferent
|
|
27
|
+
# access:
|
|
28
|
+
#
|
|
29
|
+
# bucket = s3.buckets[:mybucket]
|
|
30
|
+
# bucket = s3.buckets['mybucket']
|
|
31
|
+
#
|
|
32
|
+
# You can also use it to find out which buckets are in your account:
|
|
33
|
+
#
|
|
34
|
+
# s3.buckets.collect(&:name)
|
|
35
|
+
# #=> ['bucket1', 'bucket2', ...]
|
|
36
|
+
#
|
|
37
|
+
class BucketCollection
|
|
38
|
+
|
|
39
|
+
include Model
|
|
40
|
+
include Enumerable
|
|
41
|
+
|
|
42
|
+
# Creates and returns a new Bucket. For example:
|
|
43
|
+
#
|
|
44
|
+
# @example
|
|
45
|
+
#
|
|
46
|
+
# bucket = s3.buckets.create('my-bucket')
|
|
47
|
+
# bucket.name #=> "my-bucket"
|
|
48
|
+
# bucket.exists? #=> true
|
|
49
|
+
#
|
|
50
|
+
# @param [String] bucket_name
|
|
51
|
+
# @param [Hash] options
|
|
52
|
+
# @option options [String] :location_constraint (nil) The location
|
|
53
|
+
# where the bucket should be created. Defaults to the classic
|
|
54
|
+
# US region.
|
|
55
|
+
# @option options [String] :acl (:private) Sets the ACL of the bucket
|
|
56
|
+
# you are creating. Valid Values include :private, :public_read,
|
|
57
|
+
# :public_read_write, :authenticated_read, :bucket_owner_read and
|
|
58
|
+
# :bucket_owner_full_control
|
|
59
|
+
# @return [Bucket]
|
|
60
|
+
def create(bucket_name, options = {})
|
|
61
|
+
|
|
62
|
+
# auto set the location constraint for the user if it is not
|
|
63
|
+
# passed in and the endpoint is not the us-standard region. don't
|
|
64
|
+
# override the location constraint though, even it is wrong,
|
|
65
|
+
unless
|
|
66
|
+
config.s3_endpoint == 's3.amazonaws.com' or
|
|
67
|
+
options[:location_constraint]
|
|
68
|
+
then
|
|
69
|
+
options[:location_constraint] = case config.s3_endpoint
|
|
70
|
+
when 's3-eu-west-1.amazonaws.com' then 'EU'
|
|
71
|
+
else config.s3_endpoint.match(/^s3-(.*)\.amazonaws\.com$/)[1]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
client.create_bucket(options.merge(:bucket_name => bucket_name))
|
|
76
|
+
bucket_named(bucket_name)
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns the Bucket with the given name.
|
|
81
|
+
#
|
|
82
|
+
# Makes no requests. The returned bucket object can
|
|
83
|
+
# be used to make requets for the bucket and its objects.
|
|
84
|
+
#
|
|
85
|
+
# @example
|
|
86
|
+
#
|
|
87
|
+
# bucket = s3.buckets[:mybucket],
|
|
88
|
+
# bucket = s3.buckets['mybucket'],
|
|
89
|
+
#
|
|
90
|
+
# @param [String] bucket_name
|
|
91
|
+
# @return [Bucket]
|
|
92
|
+
def [] bucket_name
|
|
93
|
+
bucket_named(bucket_name)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Iterates the buckets in this collection.
|
|
97
|
+
#
|
|
98
|
+
# @example
|
|
99
|
+
#
|
|
100
|
+
# s3.buckets.each do |bucket|
|
|
101
|
+
# puts bucket.name
|
|
102
|
+
# end
|
|
103
|
+
#
|
|
104
|
+
# @return [nil]
|
|
105
|
+
def each &block
|
|
106
|
+
response = client.list_buckets
|
|
107
|
+
response.buckets.each do |b|
|
|
108
|
+
yield(bucket_named(b.name, response.owner))
|
|
109
|
+
end
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @private
|
|
114
|
+
private
|
|
115
|
+
def bucket_named name, owner = nil
|
|
116
|
+
S3::Bucket.new(name.to_s, :owner => owner, :config => config)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
|
|
14
|
+
require 'aws/model'
|
|
15
|
+
require 'aws/s3/object_version'
|
|
16
|
+
require 'aws/s3/prefix_and_delimiter_collection'
|
|
17
|
+
|
|
18
|
+
module AWS
|
|
19
|
+
class S3
|
|
20
|
+
|
|
21
|
+
# A collection of versioned objects for the entire bucket.
|
|
22
|
+
#
|
|
23
|
+
# @see PrefixedCollection
|
|
24
|
+
class BucketVersionCollection
|
|
25
|
+
|
|
26
|
+
include Model
|
|
27
|
+
include Enumerable
|
|
28
|
+
include PrefixAndDelimiterCollection
|
|
29
|
+
|
|
30
|
+
# @param [Bucket] bucket
|
|
31
|
+
def initialize bucket, options = {}
|
|
32
|
+
@bucket = bucket
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Bucket] The bucket this collection belongs to.
|
|
37
|
+
attr_reader :bucket
|
|
38
|
+
|
|
39
|
+
# @return [ObjectVersion] Returns the most recently created object
|
|
40
|
+
# version in the entire bucket.
|
|
41
|
+
def latest
|
|
42
|
+
self.find{|version| true }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Yields once for each version in the bucket.
|
|
46
|
+
#
|
|
47
|
+
# @yield [object_version]
|
|
48
|
+
# @yieldparam [ObjectVersion] object_version
|
|
49
|
+
# @return nil
|
|
50
|
+
def each options = {}, █ super; end
|
|
51
|
+
|
|
52
|
+
# @private
|
|
53
|
+
protected
|
|
54
|
+
def each_member_in_page(page, &block)
|
|
55
|
+
super
|
|
56
|
+
page.versions.each do |version|
|
|
57
|
+
object_version =
|
|
58
|
+
ObjectVersion.new(bucket.objects[version.key],
|
|
59
|
+
version.version_id,
|
|
60
|
+
:delete_marker => version.delete_marker?)
|
|
61
|
+
yield(object_version)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @private
|
|
66
|
+
protected
|
|
67
|
+
def list_request(options)
|
|
68
|
+
client.list_object_versions(options)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @private
|
|
72
|
+
protected
|
|
73
|
+
def limit_param; :max_keys; end
|
|
74
|
+
|
|
75
|
+
# @private
|
|
76
|
+
protected
|
|
77
|
+
def pagination_markers; super + [:version_id_marker]; end
|
|
78
|
+
|
|
79
|
+
# @private
|
|
80
|
+
protected
|
|
81
|
+
def page_size(resp); super + resp.versions.size; end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,999 @@
|
|
|
1
|
+
# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
|
5
|
+
# the License is located at
|
|
6
|
+
#
|
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
|
8
|
+
#
|
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
# language governing permissions and limitations under the License.
|
|
13
|
+
|
|
14
|
+
require 'aws/s3/request'
|
|
15
|
+
require 'aws/http/response'
|
|
16
|
+
require 'aws/base_client'
|
|
17
|
+
require 'aws/s3/errors'
|
|
18
|
+
require 'aws/s3/data_options'
|
|
19
|
+
require 'aws/s3/access_control_list'
|
|
20
|
+
require 'aws/s3/policy'
|
|
21
|
+
require 'aws/s3/client/xml'
|
|
22
|
+
require 'pathname'
|
|
23
|
+
require 'stringio'
|
|
24
|
+
require 'json'
|
|
25
|
+
require 'rexml/document'
|
|
26
|
+
|
|
27
|
+
module AWS
|
|
28
|
+
class S3
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Provides a low-level client to Amazon S3:
|
|
32
|
+
#
|
|
33
|
+
# * Each method makes exactly one request to S3, and no two
|
|
34
|
+
# methods make the same type of request.
|
|
35
|
+
#
|
|
36
|
+
# * These methods hide the details of how request parameters are
|
|
37
|
+
# sent to S3; for example:
|
|
38
|
+
#
|
|
39
|
+
# client.set_bucket_acl(# controls which host to connect to
|
|
40
|
+
# :bucket_name => "mybucket",
|
|
41
|
+
# # the request payload
|
|
42
|
+
# :acl => [{ :grantee => "..." }])
|
|
43
|
+
#
|
|
44
|
+
# * These methods return subclasses of Response, so that you can
|
|
45
|
+
# always get access to the request that was made and the raw
|
|
46
|
+
# HTTP response. You can also access S3-specific response
|
|
47
|
+
# metadata. For example:
|
|
48
|
+
#
|
|
49
|
+
# response = client.list_buckets
|
|
50
|
+
# response.http_request.http_method # => "GET"
|
|
51
|
+
# response.http_response.body # => "<ListAllMyBucketsResult xmlns..."
|
|
52
|
+
# response.request_id # => "32FE2CEB32F5EE25"
|
|
53
|
+
# # (S3-specific metadata)
|
|
54
|
+
#
|
|
55
|
+
# * This client attempts to raise ArgumentError for any invalid
|
|
56
|
+
# requests it can detect before sending a request to the
|
|
57
|
+
# service. For example:
|
|
58
|
+
#
|
|
59
|
+
# begin
|
|
60
|
+
# client.create_bucket
|
|
61
|
+
# rescue ArgumentError => e
|
|
62
|
+
# puts e # prints "The bucket_name parameter is
|
|
63
|
+
# # required"
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# * Each method can take an +:async+ to turn it into an
|
|
67
|
+
# asynchronous operation. Instead of blocking on the response
|
|
68
|
+
# to the service call, the method will return a handle on the
|
|
69
|
+
# response. For example:
|
|
70
|
+
#
|
|
71
|
+
# response = client.list_buckets(:async => true)
|
|
72
|
+
# response.on_success { p response.buckets.map(&:name) }
|
|
73
|
+
#
|
|
74
|
+
# @private
|
|
75
|
+
class Client < BaseClient
|
|
76
|
+
|
|
77
|
+
API_VERSION = '2006-03-01'
|
|
78
|
+
|
|
79
|
+
XMLNS = "http://s3.amazonaws.com/doc/#{API_VERSION}/"
|
|
80
|
+
|
|
81
|
+
include DataOptions
|
|
82
|
+
|
|
83
|
+
configure_client
|
|
84
|
+
|
|
85
|
+
protected
|
|
86
|
+
def self.bucket_method(method_name, verb, *args, &block)
|
|
87
|
+
method_options = (args.pop if args.last.kind_of?(Hash)) || {}
|
|
88
|
+
xml_grammar = (args.pop if args.last.respond_to?(:parse))
|
|
89
|
+
verb = verb.to_s.upcase
|
|
90
|
+
subresource = args.first
|
|
91
|
+
|
|
92
|
+
add_client_request_method(method_name, :xml_grammar => xml_grammar) do
|
|
93
|
+
|
|
94
|
+
configure_request do |req, options|
|
|
95
|
+
require_bucket_name!(options[:bucket_name])
|
|
96
|
+
req.http_method = verb
|
|
97
|
+
req.bucket = options[:bucket_name]
|
|
98
|
+
req.add_param(subresource) if subresource
|
|
99
|
+
|
|
100
|
+
if header_options = method_options[:header_options]
|
|
101
|
+
header_options.each do |(option_name, header)|
|
|
102
|
+
req.headers[header] = options[option_name] if
|
|
103
|
+
options[option_name]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
instance_eval(&block) if block
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
protected
|
|
114
|
+
def self.object_method(method_name, verb, *args, &block)
|
|
115
|
+
bucket_method(method_name, verb, *args) do
|
|
116
|
+
configure_request do |req, options|
|
|
117
|
+
validate_key!(options[:key])
|
|
118
|
+
super(req, options)
|
|
119
|
+
req.key = options[:key]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
instance_eval(&block) if block
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
public
|
|
127
|
+
|
|
128
|
+
bucket_method(:create_bucket, :put) do
|
|
129
|
+
configure_request do |req, options|
|
|
130
|
+
validate_bucket_name!(options[:bucket_name])
|
|
131
|
+
req.canned_acl = options[:acl]
|
|
132
|
+
if location = options[:location_constraint]
|
|
133
|
+
xmlns = "http://s3.amazonaws.com/doc/#{API_VERSION}/"
|
|
134
|
+
req.body = <<-XML
|
|
135
|
+
<CreateBucketConfiguration xmlns="#{xmlns}">
|
|
136
|
+
<LocationConstraint>#{location}</LocationConstraint>
|
|
137
|
+
</CreateBucketConfiguration>
|
|
138
|
+
XML
|
|
139
|
+
end
|
|
140
|
+
super(req, options)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
##
|
|
145
|
+
# Deletes a bucket.
|
|
146
|
+
#
|
|
147
|
+
# == Required Options
|
|
148
|
+
#
|
|
149
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
150
|
+
bucket_method(:delete_bucket, :delete)
|
|
151
|
+
|
|
152
|
+
##
|
|
153
|
+
# Lists the buckets in the account.
|
|
154
|
+
add_client_request_method(:list_buckets) do
|
|
155
|
+
configure_request do |req, options|
|
|
156
|
+
req.http_method = "GET"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
process_response do |resp|
|
|
160
|
+
XML::ListBuckets.parse(resp.http_response.body, :context => resp)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
##
|
|
165
|
+
# Sets the access policy for a bucket.
|
|
166
|
+
#
|
|
167
|
+
# == Required Options
|
|
168
|
+
#
|
|
169
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
170
|
+
#
|
|
171
|
+
# * +:policy+ -- The new policy. This can be a string (which
|
|
172
|
+
# is assumed to contain a valid policy expressed in JSON), a
|
|
173
|
+
# Policy or any object that responds to +to_json+.
|
|
174
|
+
#
|
|
175
|
+
# == Response
|
|
176
|
+
#
|
|
177
|
+
# The response contains only the standard fields.
|
|
178
|
+
bucket_method(:set_bucket_policy, :put, 'policy') do
|
|
179
|
+
|
|
180
|
+
configure_request do |req, options|
|
|
181
|
+
require_policy!(options[:policy])
|
|
182
|
+
super(req, options)
|
|
183
|
+
policy = options[:policy]
|
|
184
|
+
policy = policy.to_json unless policy.respond_to?(:to_str)
|
|
185
|
+
req.body = policy
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
##
|
|
191
|
+
# Gets the access policy for a bucket.
|
|
192
|
+
#
|
|
193
|
+
# == Required Options
|
|
194
|
+
#
|
|
195
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
196
|
+
#
|
|
197
|
+
# == Response
|
|
198
|
+
#
|
|
199
|
+
# A successful response will have a +policy+ method that
|
|
200
|
+
# returns an instance of Policy.
|
|
201
|
+
#
|
|
202
|
+
bucket_method(:get_bucket_policy, :get, 'policy') do
|
|
203
|
+
process_response do |resp|
|
|
204
|
+
# FIXME: this makes unit testing easier, but is there something
|
|
205
|
+
# we should be doing in case of invalid JSON from the service?
|
|
206
|
+
policy = Policy.from_json(resp.http_response.body) rescue nil
|
|
207
|
+
MetaUtils.extend_method(resp, :policy) { policy }
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
##
|
|
212
|
+
# Deletes the access policy for a bucket.
|
|
213
|
+
#
|
|
214
|
+
# == Required Options
|
|
215
|
+
#
|
|
216
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
217
|
+
#
|
|
218
|
+
bucket_method(:delete_bucket_policy, :delete, 'policy')
|
|
219
|
+
|
|
220
|
+
bucket_method(:set_bucket_versioning, :put, 'versioning') do
|
|
221
|
+
configure_request do |req, options|
|
|
222
|
+
state = options[:state].to_s.downcase.capitalize
|
|
223
|
+
unless state =~ /^(Enabled|Suspended)$/
|
|
224
|
+
raise ArgumentError, "invalid versioning state `#{state}`"
|
|
225
|
+
end
|
|
226
|
+
super(req, options)
|
|
227
|
+
req.body = <<-XML.strip
|
|
228
|
+
<VersioningConfiguration xmlns="#{XMLNS}">
|
|
229
|
+
<Status>#{state}</Status>
|
|
230
|
+
</VersioningConfiguration>
|
|
231
|
+
XML
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
##
|
|
236
|
+
# Gets the bucket's location constraint.
|
|
237
|
+
# @return [String] The bucket location constraint. Returns nil if
|
|
238
|
+
# the bucket was created in the US classic region.
|
|
239
|
+
bucket_method(:get_bucket_location, :get, 'location') do
|
|
240
|
+
process_response do |response|
|
|
241
|
+
regex = />(.*)<\/LocationConstraint>/
|
|
242
|
+
matches = response.http_response.body.match(regex)
|
|
243
|
+
location = matches ? matches[1] : nil
|
|
244
|
+
MetaUtils.extend_method(response, :location_constraint) { location }
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
bucket_method(:get_bucket_versioning, :get, 'versioning',
|
|
249
|
+
XML::GetBucketVersioning)
|
|
250
|
+
|
|
251
|
+
bucket_method(:list_object_versions, :get, 'versions',
|
|
252
|
+
XML::ListObjectVersions) do
|
|
253
|
+
|
|
254
|
+
configure_request do |req, options|
|
|
255
|
+
super(req, options)
|
|
256
|
+
params = %w(delimiter key_marker max_keys prefix version_id_marker)
|
|
257
|
+
params.each do |param|
|
|
258
|
+
if options[param.to_sym]
|
|
259
|
+
req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
##
|
|
267
|
+
# Sets the access control list for a bucket.
|
|
268
|
+
#
|
|
269
|
+
# == Required Options
|
|
270
|
+
#
|
|
271
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
272
|
+
#
|
|
273
|
+
# * +:acl+ -- The new acl. This can be any of the following:
|
|
274
|
+
# * An XML policy as a string (which is passed to S3 uninterpreted)
|
|
275
|
+
# * An AccessControlList object
|
|
276
|
+
# * Any object that responds to +to_xml+
|
|
277
|
+
# * Any Hash that is acceptable as an argument to
|
|
278
|
+
# AccessControlList#initialize.
|
|
279
|
+
#
|
|
280
|
+
# == Response
|
|
281
|
+
#
|
|
282
|
+
# The response contains only the standard fields.
|
|
283
|
+
bucket_method(:set_bucket_acl, :put, 'acl') do
|
|
284
|
+
configure_request do |req, options|
|
|
285
|
+
require_acl!(options[:acl])
|
|
286
|
+
super(req, options)
|
|
287
|
+
if options[:acl].kind_of?(Hash)
|
|
288
|
+
req.body = AccessControlList.new(options[:acl]).to_xml
|
|
289
|
+
elsif options[:acl].respond_to?(:to_str)
|
|
290
|
+
req.body = options[:acl]
|
|
291
|
+
else
|
|
292
|
+
req.body = options[:acl].to_xml
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
##
|
|
298
|
+
# Gets the access control list for a bucket.
|
|
299
|
+
#
|
|
300
|
+
# == Required Options
|
|
301
|
+
#
|
|
302
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
303
|
+
#
|
|
304
|
+
# == Response
|
|
305
|
+
#
|
|
306
|
+
# A successful response will have an +acl+ method that
|
|
307
|
+
# returns an instance of AccessControlList.
|
|
308
|
+
#
|
|
309
|
+
bucket_method(:get_bucket_acl, :get, 'acl',
|
|
310
|
+
XML::GetBucketAcl)
|
|
311
|
+
|
|
312
|
+
##
|
|
313
|
+
# Sets the access control list for an object.
|
|
314
|
+
#
|
|
315
|
+
# == Required Options
|
|
316
|
+
#
|
|
317
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
318
|
+
#
|
|
319
|
+
# * +:key+ -- The key of the object.
|
|
320
|
+
#
|
|
321
|
+
# * +:acl+ -- The new acl. This can be a string (which is
|
|
322
|
+
# assumed to contain a valid ACL expressed in XML), a
|
|
323
|
+
# AccessControlList or any object whose +to_xml+ returns a
|
|
324
|
+
# valid ACL expressed in XML.
|
|
325
|
+
#
|
|
326
|
+
# == Response
|
|
327
|
+
#
|
|
328
|
+
# The response contains only the standard fields.
|
|
329
|
+
object_method(:set_object_acl, :put, 'acl') do
|
|
330
|
+
configure_request do |req, options|
|
|
331
|
+
require_acl!(options[:acl])
|
|
332
|
+
super(req, options)
|
|
333
|
+
if options[:acl].kind_of?(Hash)
|
|
334
|
+
req.body = AccessControlList.new(options[:acl]).to_xml
|
|
335
|
+
elsif options[:acl].respond_to?(:to_str)
|
|
336
|
+
req.body = options[:acl]
|
|
337
|
+
else
|
|
338
|
+
req.body = options[:acl].to_xml
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
##
|
|
344
|
+
# Gets the access control list for an object.
|
|
345
|
+
#
|
|
346
|
+
# == Required Options
|
|
347
|
+
#
|
|
348
|
+
# * +:bucket_name+ -- The name of the bucket.
|
|
349
|
+
#
|
|
350
|
+
# * +:key+ -- The key of the object.
|
|
351
|
+
#
|
|
352
|
+
# == Response
|
|
353
|
+
#
|
|
354
|
+
# A successful response will have an +acl+ method that
|
|
355
|
+
# returns an instance of AccessControlList.
|
|
356
|
+
object_method(:get_object_acl, :get, 'acl',
|
|
357
|
+
XML::GetBucketAcl)
|
|
358
|
+
|
|
359
|
+
##
|
|
360
|
+
# Puts data into an object, replacing the current contents.
|
|
361
|
+
#
|
|
362
|
+
# == Required Options
|
|
363
|
+
#
|
|
364
|
+
# * +:bucket_name+ -- The name of the bucket that will contain the data.
|
|
365
|
+
#
|
|
366
|
+
# * +:key+ -- The key under which the data will be saved.
|
|
367
|
+
#
|
|
368
|
+
# * +:data+ -- The data to upload. This can be provided as an option
|
|
369
|
+
# or when using block form (see below). Valid values include:
|
|
370
|
+
#
|
|
371
|
+
# * A string
|
|
372
|
+
#
|
|
373
|
+
# * A Pathname object.
|
|
374
|
+
#
|
|
375
|
+
# * Any object with +read+ and +eof?+ methods that behave
|
|
376
|
+
# like Ruby's IO class (e.g. IO, File, Tempfile, StringIO, etc).
|
|
377
|
+
# The object must support the following access methods:
|
|
378
|
+
#
|
|
379
|
+
# read # all at once
|
|
380
|
+
# read(length) until eof? # in chunks
|
|
381
|
+
#
|
|
382
|
+
# == Optional
|
|
383
|
+
#
|
|
384
|
+
# * +:content_length+ -- Required if you are using block form to
|
|
385
|
+
# write data or if it is not possible to determine the size of
|
|
386
|
+
# +:data+. Best effort is made to determine the content length of
|
|
387
|
+
# strings, files, tempfiles, io objects, and any object that responds
|
|
388
|
+
# to #length or #size.
|
|
389
|
+
#
|
|
390
|
+
# * +:metadata+ -- A hash of metadata to be included with the
|
|
391
|
+
# object. These will be sent to S3 as headers prefixed with
|
|
392
|
+
# +x-amz-meta+.
|
|
393
|
+
#
|
|
394
|
+
# * +:acl+ -- A canned access control policy, valid values are:
|
|
395
|
+
# * +:private+
|
|
396
|
+
# * +:public_read+
|
|
397
|
+
# * ...
|
|
398
|
+
# Defaults to +:private+
|
|
399
|
+
#
|
|
400
|
+
# * +:storage_class+ -- Controls whether Reduced Redundancy
|
|
401
|
+
# Storage is enabled for the object. Valid values are
|
|
402
|
+
# +:standard+ (the default) or +:reduced_redundancy+
|
|
403
|
+
#
|
|
404
|
+
# * +:cache_control+ -- Can be used to specify caching
|
|
405
|
+
# behavior [...]
|
|
406
|
+
#
|
|
407
|
+
# * +:content_disposition+ -- Specifies presentational
|
|
408
|
+
# information [...]
|
|
409
|
+
#
|
|
410
|
+
# * +:content_encoding+ -- Specifies what content encodings
|
|
411
|
+
# have been [...]
|
|
412
|
+
#
|
|
413
|
+
# * +:content_md5+ -- The base64 encoded 128-bit [...]
|
|
414
|
+
#
|
|
415
|
+
# * +:content_type+ -- A standard MIME type describing [...]
|
|
416
|
+
#
|
|
417
|
+
# == Block Form
|
|
418
|
+
#
|
|
419
|
+
# In block form, this method yields a stream to the block that
|
|
420
|
+
# accepts data chunks. For example:
|
|
421
|
+
#
|
|
422
|
+
# s3_client.put_object(
|
|
423
|
+
# :bucket_name => 'mybucket',
|
|
424
|
+
# :key => 'some/key'
|
|
425
|
+
# :content_length => File.size('myfile')
|
|
426
|
+
# ) do |buffer|
|
|
427
|
+
#
|
|
428
|
+
# File.open('myfile') do |io|
|
|
429
|
+
# buffer.write(io.read(length)) until io.eof?
|
|
430
|
+
# end
|
|
431
|
+
#
|
|
432
|
+
# end
|
|
433
|
+
#
|
|
434
|
+
# This form is useful if you need finer control over how
|
|
435
|
+
# potentially large amounts of data are read from another
|
|
436
|
+
# source before being sent to S3; for example, if you are
|
|
437
|
+
# using a non-blocking IO model and reading from a large file
|
|
438
|
+
# on disk or from another network stream. Some HTTP handlers
|
|
439
|
+
# do not support streaming request bodies, so if you plan to
|
|
440
|
+
# upload large objects using this interface you should make
|
|
441
|
+
# sure the HTTP handler you configure for the client meets
|
|
442
|
+
# your needs.
|
|
443
|
+
#
|
|
444
|
+
# == Response
|
|
445
|
+
#
|
|
446
|
+
# If bucket versioning is enabled, a successful response will
|
|
447
|
+
# have a +version_id+ method that returns the version ID of
|
|
448
|
+
# the version that was written in the request.
|
|
449
|
+
#
|
|
450
|
+
object_method(:put_object, :put,
|
|
451
|
+
:header_options => {
|
|
452
|
+
:content_md5 => 'Content-MD5',
|
|
453
|
+
:cache_control => 'Cache-Control',
|
|
454
|
+
:content_disposition => 'Content-Disposition',
|
|
455
|
+
:content_encoding => 'Content-Encoding',
|
|
456
|
+
:content_type => 'Content-Type',
|
|
457
|
+
:storage_class => 'x-amz-storage-class'
|
|
458
|
+
}) do
|
|
459
|
+
configure_request do |request, options, block|
|
|
460
|
+
super(request, options)
|
|
461
|
+
set_request_data(request, options, block)
|
|
462
|
+
request.metadata = options[:metadata]
|
|
463
|
+
request.canned_acl = options[:acl]
|
|
464
|
+
request.storage_class = options[:storage_class]
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
process_response do |response|
|
|
468
|
+
MetaUtils.extend_method(response, :version_id) do
|
|
469
|
+
response.http_response.header('x-amz-version-id')
|
|
470
|
+
end
|
|
471
|
+
MetaUtils.extend_method(response, :etag) do
|
|
472
|
+
response.http_response.header('ETag')
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
simulate_response do |response|
|
|
477
|
+
MetaUtils.extend_method(response, :etag) { "abc123" }
|
|
478
|
+
MetaUtils.extend_method(response, :version_id) { nil }
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
##
|
|
483
|
+
# Gets the data for a key.
|
|
484
|
+
#
|
|
485
|
+
# == Required Options
|
|
486
|
+
#
|
|
487
|
+
# * +:bucket_name+ -- The name of the bucket that contains the data.
|
|
488
|
+
#
|
|
489
|
+
# * +:key+ -- The key under which the data exists.
|
|
490
|
+
#
|
|
491
|
+
# == Optional
|
|
492
|
+
#
|
|
493
|
+
# * +:if_modified_since+ -- A Time object; if specified, the
|
|
494
|
+
# response will contain an additional +modified?+ method that
|
|
495
|
+
# returns true if the object was modified after the given
|
|
496
|
+
# time. If +modified?+ returns false, the +data+ method of
|
|
497
|
+
# the response will return +nil+.
|
|
498
|
+
#
|
|
499
|
+
# * +:if_unmodified_since+ -- A Time object; if specified, the
|
|
500
|
+
# response will contain an additional +unmodified?+ method
|
|
501
|
+
# that returns true if the object was not modified after the
|
|
502
|
+
# given time. If +unmodified?+ returns false, the +data+
|
|
503
|
+
# method of the response will return +nil+.
|
|
504
|
+
#
|
|
505
|
+
# * +:if_match+ -- A string; if specified, the response will
|
|
506
|
+
# contain an additional +matches?+ method that returns true
|
|
507
|
+
# if the object ETag matches the value for this option. If
|
|
508
|
+
# +matches?+ returns false, the +data+ method of the
|
|
509
|
+
# response will return +nil+.
|
|
510
|
+
#
|
|
511
|
+
# * +:if_none_match+ -- A string; if specified, the response
|
|
512
|
+
# will contain an additional +matches?+ method that returns
|
|
513
|
+
# true if and only if the object ETag matches the value for
|
|
514
|
+
# this option. If +matches?+ returns true, the +data+
|
|
515
|
+
# method of the response will return +nil+.
|
|
516
|
+
#
|
|
517
|
+
# * +:to+ -- A destination for the data. Valid values:
|
|
518
|
+
#
|
|
519
|
+
# * The path to a file as a string
|
|
520
|
+
#
|
|
521
|
+
# * A Pathname object
|
|
522
|
+
#
|
|
523
|
+
# * Any object that supports <code>write(data)</code> and
|
|
524
|
+
# +close+ methods like Ruby's IO class
|
|
525
|
+
#
|
|
526
|
+
# * +:range+ -- TODO: figure out the format for this
|
|
527
|
+
# parameter.
|
|
528
|
+
#
|
|
529
|
+
# == Response
|
|
530
|
+
#
|
|
531
|
+
# A successful response will have some combination of the
|
|
532
|
+
# following methods:
|
|
533
|
+
#
|
|
534
|
+
# * +data+ -- The object data as a string. This will return
|
|
535
|
+
# +nil+ if one of the conditional options above is specified
|
|
536
|
+
# and the condition is not met. It will also return +nil+
|
|
537
|
+
# if +deleted?+ returns true. It will not be present if the
|
|
538
|
+
# +:to+ option is specified.
|
|
539
|
+
#
|
|
540
|
+
# * +modified?+, +unmodified?+, +matches?+ -- These will be
|
|
541
|
+
# present as documented in the conditional options above.
|
|
542
|
+
#
|
|
543
|
+
# * +version_id+ -- Returns the version ID of the object that
|
|
544
|
+
# was written (only for versioned buckets).
|
|
545
|
+
#
|
|
546
|
+
# * +deleted?+ -- This will return +true+ if the bucket has
|
|
547
|
+
# versioning enabled and the object retrieved was a delete
|
|
548
|
+
# marker.
|
|
549
|
+
object_method(:get_object, :get,
|
|
550
|
+
:header_options => {
|
|
551
|
+
:range => "Range",
|
|
552
|
+
:if_modified_since => "If-Modified-Since",
|
|
553
|
+
:if_unmodified_since => "If-Unmodified-Since",
|
|
554
|
+
:if_match => "If-Match",
|
|
555
|
+
:if_none_match => "If-None-Match"
|
|
556
|
+
}) do
|
|
557
|
+
configure_request do |req, options|
|
|
558
|
+
super(req, options)
|
|
559
|
+
if options[:version_id]
|
|
560
|
+
req.add_param('versionId', options[:version_id])
|
|
561
|
+
end
|
|
562
|
+
["If-Modified-Since",
|
|
563
|
+
"If-Unmodified-Since"].each do |date_header|
|
|
564
|
+
case value = req.headers[date_header]
|
|
565
|
+
when DateTime
|
|
566
|
+
req.headers[date_header] = Time.parse(value.to_s).rfc2822
|
|
567
|
+
when Time
|
|
568
|
+
req.headers[date_header] = value.rfc2822
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
process_response do |resp|
|
|
574
|
+
MetaUtils.extend_method(resp, :data) { resp.http_response.body }
|
|
575
|
+
MetaUtils.extend_method(resp, :version_id) do
|
|
576
|
+
http_response.header('x-amz-version-id')
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
object_method(:head_object, :head) do
|
|
582
|
+
configure_request do |req, options|
|
|
583
|
+
super(req, options)
|
|
584
|
+
if options[:version_id]
|
|
585
|
+
req.add_param('versionId', options[:version_id])
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
process_response do |resp|
|
|
590
|
+
|
|
591
|
+
# create a hash of user-supplied metadata
|
|
592
|
+
MetaUtils.extend_method(resp, :meta) do
|
|
593
|
+
meta = {}
|
|
594
|
+
resp.http_response.headers.each_pair do |name,value|
|
|
595
|
+
if name =~ /^x-amz-meta-(.+)$/i
|
|
596
|
+
meta[$1] = [value].flatten.join
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
meta
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# create methods for standard response headers
|
|
603
|
+
{
|
|
604
|
+
'x-amz-version-id' => :version_id,
|
|
605
|
+
'content-type' => :content_type,
|
|
606
|
+
'etag' => :etag,
|
|
607
|
+
}.each_pair do |header,method|
|
|
608
|
+
MetaUtils.extend_method(resp, method) do
|
|
609
|
+
http_response.header(header)
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
MetaUtils.extend_method(resp, :content_length) do
|
|
614
|
+
http_response.header('content-length').to_i
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
object_method(:delete_object, :delete) do
|
|
620
|
+
configure_request do |req, options|
|
|
621
|
+
super(req, options)
|
|
622
|
+
if options[:version_id]
|
|
623
|
+
req.add_param('versionId', options[:version_id])
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
process_response do |resp|
|
|
628
|
+
MetaUtils.extend_method(resp, :version_id) do
|
|
629
|
+
http_response.header('x-amz-version-id')
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
bucket_method(:list_objects, :get, XML::ListObjects) do
|
|
636
|
+
configure_request do |req, options|
|
|
637
|
+
super(req, options)
|
|
638
|
+
params = %w(delimiter marker max_keys prefix)
|
|
639
|
+
params.each do |param|
|
|
640
|
+
if options[param.to_sym]
|
|
641
|
+
req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
object_method(:initiate_multipart_upload, :post, 'uploads',
|
|
648
|
+
XML::InitiateMultipartUpload,
|
|
649
|
+
:header_options => {
|
|
650
|
+
:cache_control => 'Cache-Control',
|
|
651
|
+
:content_disposition => 'Content-Disposition',
|
|
652
|
+
:content_encoding => 'Content-Encoding',
|
|
653
|
+
:content_type => 'Content-Type',
|
|
654
|
+
:storage_class => 'x-amz-storage-class'
|
|
655
|
+
}) do
|
|
656
|
+
configure_request do |req, options|
|
|
657
|
+
super(req, options)
|
|
658
|
+
req.metadata = options[:metadata]
|
|
659
|
+
req.canned_acl = options[:acl]
|
|
660
|
+
req.storage_class = options[:storage_class]
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
bucket_method(:list_multipart_uploads,
|
|
665
|
+
:get, 'uploads',
|
|
666
|
+
XML::ListMultipartUploads) do
|
|
667
|
+
configure_request do |req, options|
|
|
668
|
+
super(req, options)
|
|
669
|
+
params = %w(delimiter key_marker max_keys) +
|
|
670
|
+
%w(upload_id_marker max_uploads prefix)
|
|
671
|
+
params.each do |param|
|
|
672
|
+
if options[param.to_sym]
|
|
673
|
+
req.add_param(param.gsub(/_/, '-'), options[param.to_sym])
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
object_method(:upload_part, :put,
|
|
680
|
+
:header_options => {
|
|
681
|
+
:content_md5 => 'Content-MD5'
|
|
682
|
+
}) do
|
|
683
|
+
configure_request do |request, options, block|
|
|
684
|
+
require_upload_id!(options[:upload_id])
|
|
685
|
+
validate!("part_number", options[:part_number]) do
|
|
686
|
+
"must not be blank" if options[:part_number].to_s.empty?
|
|
687
|
+
end
|
|
688
|
+
super(request, options)
|
|
689
|
+
set_request_data(request, options, block)
|
|
690
|
+
request.add_param('uploadId', options[:upload_id])
|
|
691
|
+
request.add_param('partNumber', options[:part_number])
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
process_response do |response|
|
|
695
|
+
MetaUtils.extend_method(response, :etag) do
|
|
696
|
+
response.http_response.header('ETag')
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
simulate_response do |response|
|
|
701
|
+
MetaUtils.extend_method(response, :etag) { "abc123" }
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
object_method(:complete_multipart_upload, :post,
|
|
706
|
+
XML::CompleteMultipartUpload) do
|
|
707
|
+
configure_request do |req, options|
|
|
708
|
+
require_upload_id!(options[:upload_id])
|
|
709
|
+
validate_parts!(options[:parts])
|
|
710
|
+
super(req, options)
|
|
711
|
+
req.add_param('uploadId', options[:upload_id])
|
|
712
|
+
parts_xml = options[:parts].map do |part|
|
|
713
|
+
"<Part>"+
|
|
714
|
+
"<PartNumber>#{part[:part_number].to_i}</PartNumber>"+
|
|
715
|
+
"<ETag>#{REXML::Text.normalize(part[:etag].to_s)}</ETag>"+
|
|
716
|
+
"</Part>"
|
|
717
|
+
end.join
|
|
718
|
+
req.body =
|
|
719
|
+
"<CompleteMultipartUpload>#{parts_xml}</CompleteMultipartUpload>"
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
process_response do |response|
|
|
723
|
+
MetaUtils.extend_method(response, :version_id) do
|
|
724
|
+
response.http_response.header('x-amz-version-id')
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
simulate_response do |response|
|
|
729
|
+
MetaUtils.extend_method(response, :version_id) { nil }
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
object_method(:abort_multipart_upload, :delete) do
|
|
734
|
+
configure_request do |req, options|
|
|
735
|
+
require_upload_id!(options[:upload_id])
|
|
736
|
+
super(req, options)
|
|
737
|
+
req.add_param('uploadId', options[:upload_id])
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
object_method(:list_parts, :get,
|
|
742
|
+
XML::ListParts) do
|
|
743
|
+
configure_request do |req, options|
|
|
744
|
+
require_upload_id!(options[:upload_id])
|
|
745
|
+
super(req, options)
|
|
746
|
+
req.add_param('uploadId', options[:upload_id])
|
|
747
|
+
req.add_param('max-parts', options[:max_parts])
|
|
748
|
+
req.add_param('part-number-marker', options[:part_number_marker])
|
|
749
|
+
end
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
##
|
|
753
|
+
# @param [Hash] options
|
|
754
|
+
# @option options [required, String] :bucket_name Name of the bucket
|
|
755
|
+
# to copy a object into.
|
|
756
|
+
# @option options [required, String] :key Where (object key) in the
|
|
757
|
+
# bucket the object should be copied to.
|
|
758
|
+
# @option options [required, String] :copy_source The name of the
|
|
759
|
+
# source bucket and key name of the source object, separated by a
|
|
760
|
+
# slash (/). This string must be URL-encoded. Additionally, the
|
|
761
|
+
# source bucket must be valid and you must have READ access to
|
|
762
|
+
# the valid source object.
|
|
763
|
+
# @option options [Symbol] :acl
|
|
764
|
+
#
|
|
765
|
+
object_method(:copy_object, :put,
|
|
766
|
+
:header_options => {
|
|
767
|
+
:copy_source => 'x-amz-copy-source',
|
|
768
|
+
:metadata_directive => 'x-amz-metadata-directive',
|
|
769
|
+
:storage_class => 'x-amz-storage-class',
|
|
770
|
+
}
|
|
771
|
+
) do
|
|
772
|
+
|
|
773
|
+
configure_request do |req, options|
|
|
774
|
+
# TODO : validate presence of copy source
|
|
775
|
+
# TODO : validate metadata directive COPY / REPLACE
|
|
776
|
+
# TODO : validate storage class STANDARD / REDUCED_REDUNDANCY
|
|
777
|
+
# TODO : add validations for storage class in other places used
|
|
778
|
+
super(req, options)
|
|
779
|
+
req.canned_acl = options[:acl]
|
|
780
|
+
req.metadata = options[:metadata]
|
|
781
|
+
req.storage_class = options[:storage_class]
|
|
782
|
+
if options[:version_id]
|
|
783
|
+
req.headers['x-amz-copy-source'] += "?versionId=#{options[:version_id]}"
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
process_response do |response|
|
|
788
|
+
MetaUtils.extend_method(response, :version_id) do
|
|
789
|
+
response.http_response.header('x-amz-version-id')
|
|
790
|
+
end
|
|
791
|
+
MetaUtils.extend_method(response, :etag) do
|
|
792
|
+
response.http_response.header('ETag')
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
protected
|
|
799
|
+
def xml_error_response? response
|
|
800
|
+
(response.http_response.status >= 300 ||
|
|
801
|
+
response.request_type == :complete_multipart_upload) and
|
|
802
|
+
XmlGrammar.parse(response.http_response.body).respond_to?(:code)
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
protected
|
|
806
|
+
def should_retry? response
|
|
807
|
+
super or
|
|
808
|
+
response.request_type == :complete_multipart_upload &&
|
|
809
|
+
xml_error_response?(response)
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
protected
|
|
813
|
+
def set_request_data request, options, block
|
|
814
|
+
request.body_stream = data_stream_from(options, &block)
|
|
815
|
+
request.headers['Content-Length'] = content_length_from(options)
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
protected
|
|
819
|
+
def new_request
|
|
820
|
+
S3::Request.new
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
module Validators
|
|
824
|
+
|
|
825
|
+
# Returns true if the given bucket name is valid.
|
|
826
|
+
def valid_bucket_name?(bucket_name)
|
|
827
|
+
validate_bucket_name!(bucket_name) rescue false
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def dns_compatible_bucket_name?(bucket_name)
|
|
831
|
+
return false if
|
|
832
|
+
!valid_bucket_name?(bucket_name) or
|
|
833
|
+
|
|
834
|
+
# Bucket names should not contain underscores (_)
|
|
835
|
+
bucket_name["_"] or
|
|
836
|
+
|
|
837
|
+
# Bucket names should be between 3 and 63 characters long
|
|
838
|
+
bucket_name.size > 63 or
|
|
839
|
+
|
|
840
|
+
# Bucket names should not end with a dash
|
|
841
|
+
bucket_name[-1,1] == '-' or
|
|
842
|
+
|
|
843
|
+
# Bucket names cannot contain two, adjacent periods
|
|
844
|
+
bucket_name['..'] or
|
|
845
|
+
|
|
846
|
+
# Bucket names cannot contain dashes next to periods
|
|
847
|
+
# (e.g., "my-.bucket.com" and "my.-bucket" are invalid)
|
|
848
|
+
(bucket_name['-.'] || bucket_name['.-'])
|
|
849
|
+
|
|
850
|
+
true
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
protected
|
|
854
|
+
def validate! name, value, &block
|
|
855
|
+
if error_msg = yield
|
|
856
|
+
raise ArgumentError, "#{name} #{error_msg}"
|
|
857
|
+
end
|
|
858
|
+
value
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
protected
|
|
862
|
+
def validate_key!(key)
|
|
863
|
+
validate!('key', key) do
|
|
864
|
+
case
|
|
865
|
+
when key.nil? || key == ''
|
|
866
|
+
'may not be blank'
|
|
867
|
+
end
|
|
868
|
+
end
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
protected
|
|
872
|
+
def require_bucket_name! bucket_name
|
|
873
|
+
if [nil, ''].include?(bucket_name)
|
|
874
|
+
raise ArgumentError, "bucket_name may not be blank"
|
|
875
|
+
end
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
# Returns true if the given bucket name is valid. If the name
|
|
880
|
+
# is invalid, an ArgumentError is raised.
|
|
881
|
+
protected
|
|
882
|
+
def validate_bucket_name!(bucket_name)
|
|
883
|
+
validate!('bucket_name', bucket_name) do
|
|
884
|
+
case
|
|
885
|
+
when bucket_name.nil? || bucket_name == ''
|
|
886
|
+
'may not be blank'
|
|
887
|
+
when bucket_name !~ /^[a-z0-9._\-]+$/
|
|
888
|
+
'may only contain lowercase letters, numbers, periods (.), ' +
|
|
889
|
+
'underscores (_), and dashes (-)'
|
|
890
|
+
when bucket_name !~ /^[a-z0-9]/
|
|
891
|
+
'must start with a letter or a number'
|
|
892
|
+
when !(3..255).include?(bucket_name.size)
|
|
893
|
+
'must be between 3 and 255 characters long'
|
|
894
|
+
when bucket_name =~ /(\d+\.){3}\d+/
|
|
895
|
+
'must not be formatted like an IP address (e.g., 192.168.5.4)'
|
|
896
|
+
when bucket_name =~ /\n/
|
|
897
|
+
'must not contain a newline character'
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
protected
|
|
903
|
+
def require_policy!(policy)
|
|
904
|
+
validate!('policy', policy) do
|
|
905
|
+
case
|
|
906
|
+
when policy.nil? || policy == ''
|
|
907
|
+
'may not be blank'
|
|
908
|
+
else
|
|
909
|
+
json_validation_message(policy)
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
protected
|
|
915
|
+
def require_acl!(acl)
|
|
916
|
+
validate!('acl', acl) do
|
|
917
|
+
case
|
|
918
|
+
when acl.kind_of?(Hash)
|
|
919
|
+
AccessControlList.new(acl).validate!
|
|
920
|
+
nil
|
|
921
|
+
when !acl.respond_to?(:to_str) && !acl.respond_to?(:to_xml)
|
|
922
|
+
"must support to_xml: #{acl.inspect}"
|
|
923
|
+
when acl.nil? || acl == ''
|
|
924
|
+
'may not be blank'
|
|
925
|
+
else
|
|
926
|
+
xml_validation_message(acl)
|
|
927
|
+
end
|
|
928
|
+
end
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
protected
|
|
932
|
+
def require_upload_id!(upload_id)
|
|
933
|
+
validate!("upload_id", upload_id) do
|
|
934
|
+
"must not be blank" if upload_id.to_s.empty?
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
protected
|
|
939
|
+
def validate_parts!(parts)
|
|
940
|
+
validate!("parts", parts) do
|
|
941
|
+
if !parts.kind_of?(Array)
|
|
942
|
+
"must not be blank"
|
|
943
|
+
elsif parts.empty?
|
|
944
|
+
"must contain at least one entry"
|
|
945
|
+
elsif !parts.all? { |p| p.kind_of?(Hash) }
|
|
946
|
+
"must be an array of hashes"
|
|
947
|
+
elsif !parts.all? { |p| p[:part_number] }
|
|
948
|
+
"must contain part_number for each part"
|
|
949
|
+
elsif !parts.all? { |p| p[:etag] }
|
|
950
|
+
"must contain etag for each part"
|
|
951
|
+
elsif parts.any? { |p| p[:part_number].to_i < 1 }
|
|
952
|
+
"must not have part numbers less than 1"
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
protected
|
|
958
|
+
def json_validation_message(obj)
|
|
959
|
+
if obj.respond_to?(:to_str)
|
|
960
|
+
obj = obj.to_str
|
|
961
|
+
elsif obj.respond_to?(:to_json)
|
|
962
|
+
obj = obj.to_json
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
error = nil
|
|
966
|
+
begin
|
|
967
|
+
JSON.parse(obj)
|
|
968
|
+
rescue => e
|
|
969
|
+
error = e
|
|
970
|
+
end
|
|
971
|
+
"contains invalid JSON: #{error}" if error
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
protected
|
|
975
|
+
def xml_validation_message(obj)
|
|
976
|
+
if obj.respond_to?(:to_str)
|
|
977
|
+
obj = obj.to_str
|
|
978
|
+
elsif obj.respond_to?(:to_xml)
|
|
979
|
+
obj = obj.to_xml
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
error = nil
|
|
983
|
+
begin
|
|
984
|
+
REXML::Document.new(obj)
|
|
985
|
+
rescue => e
|
|
986
|
+
error = e
|
|
987
|
+
end
|
|
988
|
+
"contains invalid XML: #{error}" if error
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
include Validators
|
|
994
|
+
extend Validators
|
|
995
|
+
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
end
|
|
999
|
+
end
|