aws-s3 0.1.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.
Files changed (55) hide show
  1. data/COPYING +19 -0
  2. data/INSTALL +35 -0
  3. data/README +529 -0
  4. data/Rakefile +284 -0
  5. data/bin/s3sh +4 -0
  6. data/bin/setup.rb +10 -0
  7. data/lib/aws/s3.rb +64 -0
  8. data/lib/aws/s3/acl.rb +631 -0
  9. data/lib/aws/s3/authentication.rb +218 -0
  10. data/lib/aws/s3/base.rb +232 -0
  11. data/lib/aws/s3/bittorrent.rb +58 -0
  12. data/lib/aws/s3/bucket.rb +323 -0
  13. data/lib/aws/s3/connection.rb +212 -0
  14. data/lib/aws/s3/error.rb +69 -0
  15. data/lib/aws/s3/exceptions.rb +130 -0
  16. data/lib/aws/s3/extensions.rb +186 -0
  17. data/lib/aws/s3/logging.rb +163 -0
  18. data/lib/aws/s3/object.rb +565 -0
  19. data/lib/aws/s3/owner.rb +44 -0
  20. data/lib/aws/s3/parsing.rb +138 -0
  21. data/lib/aws/s3/response.rb +180 -0
  22. data/lib/aws/s3/service.rb +43 -0
  23. data/lib/aws/s3/version.rb +12 -0
  24. data/support/faster-xml-simple/lib/faster_xml_simple.rb +115 -0
  25. data/support/faster-xml-simple/test/regression_test.rb +16 -0
  26. data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +22 -0
  27. data/support/rdoc/code_info.rb +211 -0
  28. data/test/acl_test.rb +243 -0
  29. data/test/authentication_test.rb +96 -0
  30. data/test/base_test.rb +143 -0
  31. data/test/bucket_test.rb +48 -0
  32. data/test/connection_test.rb +120 -0
  33. data/test/error_test.rb +75 -0
  34. data/test/extensions_test.rb +282 -0
  35. data/test/fixtures.rb +89 -0
  36. data/test/fixtures/buckets.yml +102 -0
  37. data/test/fixtures/errors.yml +34 -0
  38. data/test/fixtures/headers.yml +3 -0
  39. data/test/fixtures/logging.yml +15 -0
  40. data/test/fixtures/policies.yml +16 -0
  41. data/test/logging_test.rb +36 -0
  42. data/test/mocks/base.rb +89 -0
  43. data/test/object_test.rb +177 -0
  44. data/test/parsing_test.rb +82 -0
  45. data/test/remote/acl_test.rb +117 -0
  46. data/test/remote/bittorrent_test.rb +45 -0
  47. data/test/remote/bucket_test.rb +127 -0
  48. data/test/remote/logging_test.rb +82 -0
  49. data/test/remote/object_test.rb +267 -0
  50. data/test/remote/test_file.data +0 -0
  51. data/test/remote/test_helper.rb +30 -0
  52. data/test/response_test.rb +70 -0
  53. data/test/service_test.rb +26 -0
  54. data/test/test_helper.rb +82 -0
  55. metadata +125 -0
@@ -0,0 +1,34 @@
1
+ not_implemented: >
2
+ <Error>
3
+ <Code>NotImplemented</Code>
4
+ <Message>A header you provided implies functionality that is not implemented</Message>
5
+ <RequestId>D1D13A09AC92427F</RequestId>
6
+ <Header>Host</Header>
7
+ <HostId>oNZgzTTmWiovwGGwHXAzz+1vRmAJVAplS9TF7B0cuOGfEwoi7DYSTa/1Qhv90CfW</HostId>
8
+ </Error>
9
+
10
+ access_denied: >
11
+ <Error>
12
+ <Code>AccessDenied</Code>
13
+ <Message>Access Denied</Message>
14
+ <RequestId>F99F6D58B96C98E0</RequestId>
15
+ <HostId>XwCF7k3llrcEwtoHR7MusZ6ilCdF5DKDmwYpglvjKNjvwo24INCeXlEpo1M03Wxm</HostId>
16
+ </Error>
17
+
18
+ internal_error: >
19
+ <Error>
20
+ <Code>InternalError</Code>
21
+ <Message>Internal Error</Message>
22
+ <RequestId>F99F6D223B96C98E0</RequestId>
23
+ <HostId>XwCF7k3llrcEwtoHR7MusZ6ilCdF5DKDmwYpglvjKNjvwo24INCeXlEpo1M03Wxm</HostId>
24
+ </Error>
25
+
26
+ error_with_no_message: >
27
+ <Error>
28
+ <Code>InvalidArgument</Code>
29
+ <Message></Message>
30
+ <ArgumentValue>READ</ArgumentValue>
31
+ <RequestId>74A377B1C0FA2BCF</RequestId>
32
+ <HostId>cP4rqsAEtHpN6Ckv08Hr3LXjLzx15/YgyoSqzs779vMR8MrAFSodxZp96wtuMQuI</HostId>
33
+ <ArgumentName>x-amz-acl</ArgumentName>
34
+ </Error>
@@ -0,0 +1,3 @@
1
+ headers_including_one_piece_of_metadata:
2
+ x-amz-meta-test: foo
3
+ content_type: text/plain
@@ -0,0 +1,15 @@
1
+ logging_enabled: >
2
+ <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
3
+ <LoggingEnabled>
4
+ <TargetBucket>mylogs</TargetBucket>
5
+ <TargetPrefix>access_log-</TargetPrefix>
6
+ </LoggingEnabled>
7
+ </BucketLoggingStatus>
8
+
9
+ logging_disabled: >
10
+ <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
11
+ <!--<LoggingEnabled>
12
+ <TargetBucket>myLogsBucket</TargetBucket>
13
+ <TargetPrefix>add/this/prefix/to/my/log/files/access_log-</TargetPrefix>
14
+ </LoggingEnabled>-->
15
+ </BucketLoggingStatus>
@@ -0,0 +1,16 @@
1
+ policy_with_one_grant: >
2
+ <AccessControlPolicy>
3
+ <Owner>
4
+ <ID>bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1</ID>
5
+ <DisplayName>mmolina@onramp.net</DisplayName>
6
+ </Owner>
7
+ <AccessControlList>
8
+ <Grant>
9
+ <Grantee xsi:type="CanonicalUser" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
10
+ <ID>bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1</ID>
11
+ <DisplayName>mmolina@onramp.net</DisplayName>
12
+ </Grantee>
13
+ <Permission>FULL_CONTROL</Permission>
14
+ </Grant>
15
+ </AccessControlList>
16
+ </AccessControlPolicy>
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class LoggingStatusReadingTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @disabled = logging_status(:logging_disabled)
7
+ @enabled = logging_status(:logging_enabled)
8
+ @new_status = Logging::Status.new('target_bucket' => 'foo', 'target_prefix' => 'access-log-')
9
+ end
10
+
11
+ def test_logging_enabled?
12
+ assert !@disabled.logging_enabled?
13
+ assert !@new_status.logging_enabled?
14
+ assert @enabled.logging_enabled?
15
+ end
16
+
17
+ def test_passing_in_prefix_and_bucket
18
+ assert_equal 'foo', @new_status.target_bucket
19
+ assert_equal 'access-log-', @new_status.target_prefix
20
+ assert !@new_status.logging_enabled?
21
+ end
22
+
23
+ private
24
+ def logging_status(fixture)
25
+ Logging::Status.new(Parsing::XmlParser.new(Fixtures::Logging[fixture.to_s]))
26
+ end
27
+ end
28
+
29
+ class LoggingStatusWritingTest < LoggingStatusReadingTest
30
+
31
+ def setup
32
+ super
33
+ @disabled = Logging::Status.new(Parsing::XmlParser.new(@disabled.to_xml))
34
+ @enabled = Logging::Status.new(Parsing::XmlParser.new(@enabled.to_xml))
35
+ end
36
+ end
@@ -0,0 +1,89 @@
1
+ require_library_or_gem 'flexmock'
2
+
3
+ module AWS
4
+ module S3
5
+ class FakeResponse < String
6
+ attr_reader :code, :body, :headers
7
+ def initialize(options = {})
8
+ @code = options.delete(:code) || 200
9
+ @body = options.delete(:body) || ''
10
+ @headers = {'content-type' => 'application/xml'}.merge(options.delete(:headers) || {})
11
+ super(@body)
12
+ end
13
+
14
+ # For ErrorResponse
15
+ def response
16
+ self
17
+ end
18
+
19
+ def [](header)
20
+ headers[header]
21
+ end
22
+
23
+ def each(&block)
24
+ headers.each(&block)
25
+ end
26
+ alias_method :each_header, :each
27
+ end
28
+
29
+ class Base
30
+ class << self
31
+ @@responses = []
32
+ @@in_test_mode = false
33
+ @@catch_all_response = nil
34
+
35
+ def in_test_mode=(boolean)
36
+ @@in_test_mode = boolean
37
+ end
38
+
39
+ def responses
40
+ @@responses
41
+ end
42
+
43
+ def catch_all_response
44
+ @@catch_all_response
45
+ end
46
+
47
+ def reset!
48
+ responses.clear
49
+ end
50
+
51
+ def request_returns(response_data)
52
+ responses.concat [response_data].flatten.map {|data| FakeResponse.new(data)}
53
+ end
54
+
55
+ def request_always_returns(response_data, &block)
56
+ in_test_mode do
57
+ @@catch_all_response = FakeResponse.new(response_data)
58
+ yield
59
+ @@catch_all_response = nil
60
+ end
61
+ end
62
+
63
+ def in_test_mode(&block)
64
+ self.in_test_mode = true
65
+ yield
66
+ ensure
67
+ self.in_test_mode = false
68
+ end
69
+
70
+ alias_method :old_connection, :connection
71
+ def connection
72
+ if @@in_test_mode
73
+ @mock_connection ||=
74
+ begin
75
+ mock_connection = FlexMock.new
76
+ mock_connection.mock_handle(:request) do
77
+ raise 'No responses left' unless response = catch_all_response || responses.shift
78
+ response
79
+ end
80
+ mock_connection
81
+ end
82
+ else
83
+ old_connection
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,177 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ObjectTest < Test::Unit::TestCase
4
+ def setup
5
+ bucket = Bucket.new(Parsing::XmlParser.new(Fixtures::Buckets.bucket_with_one_key))
6
+ @object = bucket.objects.first
7
+ end
8
+
9
+ def test_header_settings_reader_and_writer
10
+ S3Object.in_test_mode do
11
+ headers = {'content-type' => 'text/plain'}
12
+ S3Object.request_returns :headers => headers
13
+
14
+ assert_nothing_raised do
15
+ @object.content_type
16
+ end
17
+
18
+ assert_equal 'text/plain', @object.content_type
19
+
20
+ assert_nothing_raised do
21
+ @object.content_type = 'image/jpg'
22
+ end
23
+
24
+ assert_equal 'image/jpg', @object.content_type
25
+
26
+ assert_raises(NoMethodError) do
27
+ @object.non_existant_header_setting
28
+ end
29
+ end
30
+ end
31
+
32
+ def test_key_name_validation
33
+ assert_raises(InvalidKeyName) do
34
+ S3Object.create(nil, '', 'marcel')
35
+ end
36
+
37
+ assert_raises(InvalidKeyName) do
38
+ huge_name = 'a' * 1500
39
+ S3Object.create(huge_name, '', 'marcel')
40
+ end
41
+ end
42
+
43
+ def test_object_has_owner
44
+ assert_kind_of Owner, @object.owner
45
+ end
46
+
47
+ def test_owner_attributes_are_accessible
48
+ owner = @object.owner
49
+ assert owner.id
50
+ assert owner.display_name
51
+ assert_equal 'bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1', owner.id
52
+ assert_equal 'mmolina@onramp.net', owner.display_name
53
+ end
54
+
55
+ def test_only_valid_attributes_accessible
56
+ assert_raises(NoMethodError) do
57
+ @object.owner.foo
58
+ end
59
+ end
60
+
61
+ def test_fetching_object_value_generates_value_object
62
+ S3Object.in_test_mode do
63
+ S3Object.request_returns :body => 'hello!'
64
+ value = S3Object.value('foo', 'bar')
65
+ assert_kind_of S3Object::Value, value
66
+ assert_equal 'hello!', value
67
+ end
68
+ end
69
+
70
+ def test_fetching_file_by_name_raises_when_heuristic_fails
71
+ S3Object.request_always_returns :body => Fixtures::Buckets.bucket_with_one_key do
72
+ assert_raises(NoSuchKey) do
73
+ S3Object.find('not_tongue_overload.jpg', 'marcel_molina')
74
+ end
75
+
76
+ object = nil # Block scoping
77
+ assert_nothing_raised do
78
+ object = S3Object.find('tongue_overload.jpg', 'marcel_molina')
79
+ end
80
+ assert_kind_of S3Object, object
81
+ assert_equal 'tongue_overload.jpg', object.key
82
+ end
83
+ end
84
+
85
+ def test_about
86
+ S3Object.in_test_mode do
87
+ headers = {'content-size' => '12345', 'date' => Time.now.httpdate, 'content-type' => 'application/xml'}
88
+ S3Object.request_returns :headers => headers
89
+ about = S3Object.about('foo', 'bar')
90
+ assert_kind_of S3Object::About, about
91
+ assert_equal headers, about
92
+ end
93
+ end
94
+
95
+ def test_s3object_equality
96
+ Bucket.in_test_mode do
97
+ Bucket.request_returns :body => Fixtures::Buckets.bucket_with_more_than_one_key
98
+ file1, file2 = Bucket.objects('does not matter')
99
+ assert file1 == file1
100
+ assert file2 == file2
101
+ assert !(file1 == file2) # /!\ Parens required /!\
102
+ end
103
+ end
104
+
105
+ def test_inspect
106
+ S3Object.in_test_mode do
107
+ S3Object.request_returns :body => Fixtures::Buckets.bucket_with_one_key
108
+ object = S3Object.find('tongue_overload.jpg', 'bucket does not matter')
109
+ assert object.path
110
+ assert_nothing_raised { object.inspect }
111
+ assert object.inspect[object.path]
112
+ end
113
+ end
114
+
115
+ def test_etag
116
+ S3Object.in_test_mode do
117
+ S3Object.request_returns :body => Fixtures::Buckets.bucket_with_one_key
118
+ file = S3Object.find('tongue_overload.jpg', 'bucket does not matter')
119
+ assert file.etag
120
+ assert_equal 'f21f7c4e8ea6e34b268887b07d6da745', file.etag
121
+ end
122
+ end
123
+ end
124
+
125
+ class MetadataTest < Test::Unit::TestCase
126
+ def setup
127
+ @metadata = S3Object::Metadata.new(Fixtures::Headers.headers_including_one_piece_of_metadata)
128
+ end
129
+
130
+ def test_only_metadata_is_extracted
131
+ assert @metadata.to_headers.size == 1
132
+ assert @metadata.to_headers['x-amz-meta-test']
133
+ assert_equal 'foo', @metadata.to_headers['x-amz-meta-test']
134
+ end
135
+
136
+ def test_setting_new_metadata_normalizes_name
137
+ @metadata[:bar] = 'baz'
138
+ assert @metadata.to_headers.include?('x-amz-meta-bar')
139
+ @metadata['baz'] = 'quux'
140
+ assert @metadata.to_headers.include?('x-amz-meta-baz')
141
+ @metadata['x-amz-meta-quux'] = 'whatever'
142
+ assert @metadata.to_headers.include?('x-amz-meta-quux')
143
+ end
144
+
145
+ def test_clobbering_existing_header
146
+ @metadata[:bar] = 'baz'
147
+ assert_equal 'baz', @metadata.to_headers['x-amz-meta-bar']
148
+ @metadata[:bar] = 'quux'
149
+ assert_equal 'quux', @metadata.to_headers['x-amz-meta-bar']
150
+ @metadata['bar'] = 'foo'
151
+ assert_equal 'foo', @metadata.to_headers['x-amz-meta-bar']
152
+ @metadata['x-amz-meta-bar'] = 'bar'
153
+ assert_equal 'bar', @metadata.to_headers['x-amz-meta-bar']
154
+ end
155
+
156
+ def test_invalid_metadata
157
+ @metadata[:invalid_header] = ' ' * (S3Object::Metadata::SIZE_LIMIT + 1)
158
+ assert_raises InvalidMetadataValue do
159
+ @metadata.to_headers
160
+ end
161
+ end
162
+ end
163
+
164
+ class ValueTest < Test::Unit::TestCase
165
+ def setup
166
+ @response = FakeResponse.new(:body => 'hello there')
167
+ @value = S3Object::Value.new(@response)
168
+ end
169
+
170
+ def test_value_is_set_to_response_body
171
+ assert_equal @response.body, @value
172
+ end
173
+
174
+ def test_response_is_accessible_from_value_object
175
+ assert_equal @response, @value.response
176
+ end
177
+ end
@@ -0,0 +1,82 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ class CoercibleStringTest < Test::Unit::TestCase
3
+
4
+ def test_coerce
5
+ coercions = [
6
+ ['1', 1],
7
+ ['false', false],
8
+ ['true', true],
9
+ ['2006-10-29T23:14:47.000Z', Time.parse('2006-10-29T23:14:47.000Z')],
10
+ ['Hello!', 'Hello!'],
11
+ ['false23', 'false23'],
12
+ ['03 1-2-3-Apple-Tree.mp3', '03 1-2-3-Apple-Tree.mp3']
13
+ ]
14
+
15
+ coercions.each do |before, after|
16
+ assert_nothing_raised do
17
+ assert_equal after, Parsing::CoercibleString.coerce(before)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ class TypecastingTest < Test::Unit::TestCase
24
+ # Make it easier to call methods in tests
25
+ Parsing::Typecasting.public_instance_methods.each do |method|
26
+ Parsing::Typecasting.send(:module_function, method)
27
+ end
28
+
29
+ def test_array_with_one_element_that_is_a_hash
30
+ value = [{'Available' => 'true'}]
31
+ assert_equal [{'available' => true}], Parsing::Typecasting.typecast(value)
32
+ end
33
+
34
+ def test_hash_with_one_key_whose_value_is_an_array
35
+ value = {
36
+ 'Bucket' =>
37
+ [
38
+ {'Available' => 'true'}
39
+ ]
40
+ }
41
+
42
+ expected = {
43
+ 'bucket' =>
44
+ [
45
+ {'available' => true}
46
+ ]
47
+ }
48
+ assert_equal expected, Parsing::Typecasting.typecast(value)
49
+ end
50
+
51
+ end
52
+
53
+ class XmlParserTest < Test::Unit::TestCase
54
+ def test_bucket_is_always_forced_to_be_an_array_unless_empty
55
+ one_bucket = Parsing::XmlParser.new(Fixtures::Buckets.bucket_list_with_one_bucket)
56
+ more_than_one = Parsing::XmlParser.new(Fixtures::Buckets.bucket_list_with_more_than_one_bucket)
57
+
58
+ [one_bucket, more_than_one].each do |bucket_list|
59
+ assert_kind_of Array, bucket_list['buckets']['bucket']
60
+ end
61
+
62
+ no_buckets = Parsing::XmlParser.new(Fixtures::Buckets.empty_bucket_list)
63
+ assert no_buckets.has_key?('buckets')
64
+ assert_nil no_buckets['buckets']
65
+ end
66
+
67
+ def test_bucket_contents_are_forced_to_be_an_array_unless_empty
68
+ one_key = Parsing::XmlParser.new(Fixtures::Buckets.bucket_with_one_key)
69
+ more_than_one = Parsing::XmlParser.new(Fixtures::Buckets.bucket_with_more_than_one_key)
70
+ [one_key, more_than_one].each do |bucket_with_contents|
71
+ assert_kind_of Array, bucket_with_contents['contents']
72
+ end
73
+
74
+ no_keys = Parsing::XmlParser.new(Fixtures::Buckets.empty_bucket)
75
+ assert !no_keys.has_key?('contents')
76
+ end
77
+
78
+ def test_policy_grants_are_always_an_array
79
+ policy = Parsing::XmlParser.new(Fixtures::Policies.policy_with_one_grant)
80
+ assert_kind_of Array, policy['access_control_list']['grant']
81
+ end
82
+ end