plntr-fakes3 1.0.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CONTRIBUTING.md +50 -0
- data/DEPLOY_README.md +18 -0
- data/Dockerfile +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +76 -0
- data/ISSUE_TEMPLATE.md +9 -0
- data/Makefile +7 -0
- data/PULL_REQUEST_TEMPLATE.md +9 -0
- data/README.md +42 -0
- data/Rakefile +18 -0
- data/bin/fakes3 +6 -0
- data/fakes3.gemspec +34 -0
- data/lib/fakes3/bucket.rb +65 -0
- data/lib/fakes3/bucket_query.rb +11 -0
- data/lib/fakes3/cli.rb +71 -0
- data/lib/fakes3/errors.rb +46 -0
- data/lib/fakes3/file_store.rb +286 -0
- data/lib/fakes3/rate_limitable_file.rb +21 -0
- data/lib/fakes3/s3_object.rb +19 -0
- data/lib/fakes3/server.rb +560 -0
- data/lib/fakes3/sorted_object_list.rb +137 -0
- data/lib/fakes3/unsupported_operation.rb +4 -0
- data/lib/fakes3/util.rb +8 -0
- data/lib/fakes3/version.rb +3 -0
- data/lib/fakes3/xml_adapter.rb +222 -0
- data/lib/fakes3.rb +3 -0
- data/static/button.svg +4 -0
- data/static/logo.png +0 -0
- data/test/aws_sdk_commands_test.rb +58 -0
- data/test/aws_sdk_v2_commands_test.rb +65 -0
- data/test/boto_test.rb +25 -0
- data/test/botocmd.py +87 -0
- data/test/cli_test.rb +18 -0
- data/test/local_s3_cfg +34 -0
- data/test/minitest_helper.rb +46 -0
- data/test/post_test.rb +58 -0
- data/test/right_aws_commands_test.rb +209 -0
- data/test/s3_commands_test.rb +209 -0
- data/test/s3cmd_test.rb +52 -0
- data/test/test_helper.rb +6 -0
- metadata +255 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module FakeS3
|
5
|
+
class XmlAdapter
|
6
|
+
def self.buckets(bucket_objects)
|
7
|
+
output = ""
|
8
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
9
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
10
|
+
xml.ListAllMyBucketsResult(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |lam|
|
11
|
+
lam.Owner { |owner|
|
12
|
+
owner.ID("123")
|
13
|
+
owner.DisplayName("FakeS3")
|
14
|
+
}
|
15
|
+
lam.Buckets { |buckets|
|
16
|
+
bucket_objects.each do |bucket|
|
17
|
+
buckets.Bucket do |b|
|
18
|
+
b.Name(bucket.name)
|
19
|
+
b.CreationDate(bucket.creation_date.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
}
|
23
|
+
}
|
24
|
+
output
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.error(error)
|
28
|
+
output = ""
|
29
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
30
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
31
|
+
xml.Error { |err|
|
32
|
+
err.Code(error.code)
|
33
|
+
err.Message(error.message)
|
34
|
+
err.Resource(error.resource)
|
35
|
+
err.RequestId(1)
|
36
|
+
}
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
41
|
+
#<Error>
|
42
|
+
# <Code>NoSuchKey</Code>
|
43
|
+
# <Message>The resource you requested does not exist</Message>
|
44
|
+
# <Resource>/mybucket/myfoto.jpg</Resource>
|
45
|
+
# <RequestId>4442587FB7D0A2F9</RequestId>
|
46
|
+
#</Error>
|
47
|
+
#
|
48
|
+
def self.error_no_such_bucket(name)
|
49
|
+
output = ""
|
50
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
51
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
52
|
+
xml.Error { |err|
|
53
|
+
err.Code("NoSuchBucket")
|
54
|
+
err.Message("The resource you requested does not exist")
|
55
|
+
err.Resource(name)
|
56
|
+
err.RequestId(1)
|
57
|
+
}
|
58
|
+
output
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.error_bucket_not_empty(name)
|
62
|
+
output = ""
|
63
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
64
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
65
|
+
xml.Error { |err|
|
66
|
+
err.Code("BucketNotEmpty")
|
67
|
+
err.Message("The bucket you tried to delete is not empty.")
|
68
|
+
err.Resource(name)
|
69
|
+
err.RequestId(1)
|
70
|
+
}
|
71
|
+
output
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.error_no_such_key(name)
|
75
|
+
output = ""
|
76
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
77
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
78
|
+
xml.Error { |err|
|
79
|
+
err.Code("NoSuchKey")
|
80
|
+
err.Message("The specified key does not exist")
|
81
|
+
err.Key(name)
|
82
|
+
err.RequestId(1)
|
83
|
+
err.HostId(2)
|
84
|
+
}
|
85
|
+
output
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.bucket(bucket)
|
89
|
+
output = ""
|
90
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
91
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
92
|
+
xml.ListBucketResult(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |lbr|
|
93
|
+
lbr.Name(bucket.name)
|
94
|
+
lbr.Prefix
|
95
|
+
lbr.Marker
|
96
|
+
lbr.MaxKeys("1000")
|
97
|
+
lbr.IsTruncated("false")
|
98
|
+
}
|
99
|
+
output
|
100
|
+
end
|
101
|
+
|
102
|
+
# A bucket query gives back the bucket along with contents
|
103
|
+
# <Contents>
|
104
|
+
#<Key>Nelson</Key>
|
105
|
+
# <LastModified>2006-01-01T12:00:00.000Z</LastModified>
|
106
|
+
# <ETag>"828ef3fdfa96f00ad9f27c383fc9ac7f"</ETag>
|
107
|
+
# <Size>5</Size>
|
108
|
+
# <StorageClass>STANDARD</StorageClass>
|
109
|
+
# <Owner>
|
110
|
+
# <ID>bcaf161ca5fb16fd081034f</ID>
|
111
|
+
# <DisplayName>webfile</DisplayName>
|
112
|
+
# </Owner>
|
113
|
+
# </Contents>
|
114
|
+
|
115
|
+
def self.append_objects_to_list_bucket_result(lbr,objects)
|
116
|
+
return if objects.nil? or objects.size == 0
|
117
|
+
|
118
|
+
if objects.index(nil)
|
119
|
+
require 'ruby-debug'
|
120
|
+
Debugger.start
|
121
|
+
debugger
|
122
|
+
end
|
123
|
+
|
124
|
+
objects.each do |s3_object|
|
125
|
+
lbr.Contents { |contents|
|
126
|
+
contents.Key(s3_object.name)
|
127
|
+
contents.LastModified(s3_object.modified_date)
|
128
|
+
contents.ETag("\"#{s3_object.md5}\"")
|
129
|
+
contents.Size(s3_object.size)
|
130
|
+
contents.StorageClass("STANDARD")
|
131
|
+
|
132
|
+
contents.Owner { |owner|
|
133
|
+
owner.ID("abc")
|
134
|
+
owner.DisplayName("You")
|
135
|
+
}
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.append_common_prefixes_to_list_bucket_result(lbr, prefixes)
|
141
|
+
return if prefixes.nil? or prefixes.size == 0
|
142
|
+
|
143
|
+
prefixes.each do |common_prefix|
|
144
|
+
lbr.CommonPrefixes { |contents| contents.Prefix(common_prefix) }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.bucket_query(bucket_query)
|
149
|
+
output = ""
|
150
|
+
bucket = bucket_query.bucket
|
151
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
152
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
153
|
+
xml.ListBucketResult(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |lbr|
|
154
|
+
lbr.Name(bucket.name)
|
155
|
+
lbr.Prefix(bucket_query.prefix)
|
156
|
+
lbr.Marker(bucket_query.marker)
|
157
|
+
lbr.MaxKeys(bucket_query.max_keys)
|
158
|
+
lbr.IsTruncated(bucket_query.is_truncated?)
|
159
|
+
append_objects_to_list_bucket_result(lbr,bucket_query.matches)
|
160
|
+
append_common_prefixes_to_list_bucket_result(lbr, bucket_query.common_prefixes)
|
161
|
+
}
|
162
|
+
output
|
163
|
+
end
|
164
|
+
|
165
|
+
# ACL xml
|
166
|
+
def self.acl(object = nil)
|
167
|
+
output = ""
|
168
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
169
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
170
|
+
xml.AccessControlPolicy(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |acp|
|
171
|
+
acp.Owner do |owner|
|
172
|
+
owner.ID("abc")
|
173
|
+
owner.DisplayName("You")
|
174
|
+
end
|
175
|
+
acp.AccessControlList do |acl|
|
176
|
+
acl.Grant do |grant|
|
177
|
+
grant.Grantee("xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xsi:type" => "CanonicalUser") do |grantee|
|
178
|
+
grantee.ID("abc")
|
179
|
+
grantee.DisplayName("You")
|
180
|
+
end
|
181
|
+
grant.Permission("FULL_CONTROL")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
}
|
185
|
+
output
|
186
|
+
end
|
187
|
+
|
188
|
+
# <CopyObjectResult>
|
189
|
+
# <LastModified>2009-10-28T22:32:00</LastModified>
|
190
|
+
# <ETag>"9b2cf535f27731c974343645a3985328"</ETag>
|
191
|
+
# </CopyObjectResult>
|
192
|
+
def self.copy_object_result(object)
|
193
|
+
output = ""
|
194
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
195
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
196
|
+
xml.CopyObjectResult { |result|
|
197
|
+
result.LastModified(object.modified_date)
|
198
|
+
result.ETag("\"#{object.md5}\"")
|
199
|
+
}
|
200
|
+
output
|
201
|
+
end
|
202
|
+
|
203
|
+
# <CompleteMultipartUploadResult>
|
204
|
+
# <Location>http://Example-Bucket.s3.amazonaws.com/Example-Object</Location>
|
205
|
+
# <Bucket>Example-Bucket</Bucket>
|
206
|
+
# <Key>Example-Object</Key>
|
207
|
+
# <ETag>"3858f62230ac3c915f300c664312c11f-9"</ETag>
|
208
|
+
# </CompleteMultipartUploadResult>
|
209
|
+
def self.complete_multipart_result(object)
|
210
|
+
output = ""
|
211
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
212
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
213
|
+
xml.CompleteMultipartUploadResult { |result|
|
214
|
+
result.Location("TODO: implement")
|
215
|
+
result.Bucket("TODO: implement")
|
216
|
+
result.Key(object.name)
|
217
|
+
result.ETag("\"#{object.md5}\"")
|
218
|
+
}
|
219
|
+
output
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
data/lib/fakes3.rb
ADDED
data/static/button.svg
ADDED
@@ -0,0 +1,4 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="110px" height="20px" viewBox="0 0 110 20" enable-background="new 0 0 110 20" xml:space="preserve"> <image id="image0" width="110" height="20" x="0" y="0" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG4AAAAUCAMAAABbPPhuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABL1BMVEUAUJX///8AUJUAUJUA UJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUAUJUA UJUAUJUAUJUAUJUAUJUAUJUAUJU5e7Rmns59r9uAs917sNx6r9yDtN5lnc0vdK9EhLtzq9o/i8wp fsYhecQdd8Mke8U9iss6fLU4e7RyqtougccfeMNQjcx0otRjmNAhesQzhMliodVKib8+i8zT4PDI 2OyTtdyvx+ShvuD///8qf8dOlNCAstwlfMU7fbZ/st0gecQ5g8e7z+iErNibw+QyhMnq8Pj19/vf 6PTG3fAwg8jm8PhNlND5+/12rNs0hcluqNh3rdtNisDn8fl5rtvH3fBnns4xdbCcw+VhoNU5fLVo oM99sNsds+t8AAAAHXRSTlMAAAZux/NbG8+ZCNAobZoK+dNcn/VgLF+dB2/L9Kp1EagAAAABYktH RAH/Ai3eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AsECgU1BHGi1AAAAmZJREFUSMe9 lgtT1DAQxytwgoLC+X4BeiegGEUkCXqXtgkomlabqvh+P77/Z3A3SXst5Wa4cY6duUmbbPaX/HeT XhAEE5NTrcXx2ckTFQuC6Zml5Zu3Ot3x2O2V1VOnK7iJmbU7d9fvkTHZ/Qedjdm5Ae7Mw81HW0ef Thkfjbf9+EmvVQoaTC33HU2ghdGQWZGIbSuVGHF/Wzu7K6tn5z1u4em661bO4sMnFRgmo6NRKvas 39lrO0GDxecvChzDqMpFjCTjUiJH0gZOy1BIDS+JbcBXigTfQxFTHBZJ6oK+tEWx/QoEPedwHVLB pSpzkYWSVClKuDK13WGrc5RBkAgbo8HXwENOKL5nfli6oK/flIKen0dct8TlAibyEkdC+MUqaeAS lVMdc21UCj4J+GYMZOESMsEYumiOS7VB3+57QbsoaBXnc1fiUlhxrngDlykrFQwzwGTWF8VhsGWQ OleSsVxFPui7965CP3zsXbhYxYGYFEKVOIjLIFoDZz1tnq2VOCJB1IQY1y0L3KcC97mBw6XhgoEk MWDmc1DD2fCY1gzKQ5MBjpLUKI45wG4X9MuuF/PrXvtSrVQgdxmkIIWn3K4Oc08LnFsy4sAhiQ3m TEgByS5w0sSwP8qUiXHYBv3mS2X/+w9XKoODgHUmIC2h5eG2hApJE0ci0MtERAvoCXWJSzOnoR12 J+GnPwi/eq3L9WNeMc7cGYt9wskwD830gW4nBq/ddL4q65dY02CXIflvs2fuSnGJTS4Nv6L5iNfx YXbgir46u7bZH+cHqLvRnqt9Xn//+Xtsn1f483Dt+o3j+vPwD8d6fpTeP3ACAAAAJXRFWHRkYXRl OmNyZWF0ZQAyMDE2LTExLTA0VDEwOjA1OjUzLTA3OjAwJ5wXuQAAACV0RVh0ZGF0ZTptb2RpZnkA MjAxNi0xMS0wNFQxMDowNTo1My0wNzowMFbBrwUAAAAASUVORK5CYII="/>
|
4
|
+
</svg>
|
data/static/logo.png
ADDED
Binary file
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'aws-sdk-v1'
|
3
|
+
|
4
|
+
class AwsSdkCommandsTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@s3 = AWS::S3.new(:access_key_id => '123',
|
7
|
+
:secret_access_key => 'abc',
|
8
|
+
:s3_endpoint => 'localhost',
|
9
|
+
:s3_port => 10453,
|
10
|
+
:use_ssl => false)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_copy_to
|
14
|
+
bucket = @s3.buckets["test_copy_to"]
|
15
|
+
object = bucket.objects["key1"]
|
16
|
+
object.write("asdf")
|
17
|
+
|
18
|
+
assert object.exists?
|
19
|
+
object.copy_to("key2")
|
20
|
+
|
21
|
+
assert_equal 2, bucket.objects.count
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_multipart_upload
|
25
|
+
bucket = @s3.buckets["test_multipart_upload"]
|
26
|
+
object = bucket.objects["key1"]
|
27
|
+
object.write("thisisaverybigfile", :multipart_threshold => 5)
|
28
|
+
assert object.exists?
|
29
|
+
assert_equal "thisisaverybigfile", object.read
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_metadata
|
33
|
+
file_path = './test_root/test_metadata/metaobject'
|
34
|
+
FileUtils.rm_rf file_path
|
35
|
+
|
36
|
+
bucket = @s3.buckets["test_metadata"]
|
37
|
+
object = bucket.objects["metaobject"]
|
38
|
+
object.write(
|
39
|
+
'data',
|
40
|
+
# this is sent as header x-amz-storage-class
|
41
|
+
:storage_class => 'REDUCED_REDUNDANCY',
|
42
|
+
# this is sent as header x-amz-meta-custom1
|
43
|
+
:metadata => {
|
44
|
+
"custom1" => "foobar"
|
45
|
+
}
|
46
|
+
)
|
47
|
+
assert object.exists?
|
48
|
+
metadata_file = YAML.load(IO.read("#{file_path}/.fakes3_metadataFFF/metadata"))
|
49
|
+
|
50
|
+
assert metadata_file.has_key?(:custom_metadata), 'Metadata file does not contain a :custom_metadata key'
|
51
|
+
assert metadata_file[:custom_metadata].has_key?('custom1'), ':custom_metadata does not contain field "custom1"'
|
52
|
+
assert_equal 'foobar', metadata_file[:custom_metadata]['custom1'], '"custom1" does not equal expected value "foobar"'
|
53
|
+
|
54
|
+
assert metadata_file.has_key?(:amazon_metadata), 'Metadata file does not contain an :amazon_metadata key'
|
55
|
+
assert metadata_file[:amazon_metadata].has_key?('storage-class'), ':amazon_metadata does not contain field "storage-class"'
|
56
|
+
assert_equal 'REDUCED_REDUNDANCY', metadata_file[:amazon_metadata]['storage-class'], '"storage-class" does not equal expected value "REDUCED_REDUNDANCY"'
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'aws-sdk'
|
3
|
+
|
4
|
+
class AwsSdkV2CommandsTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@creds = Aws::Credentials.new('123', 'abc')
|
7
|
+
@s3 = Aws::S3::Client.new(credentials: @creds, region: 'us-east-1', endpoint: 'http://localhost:10453/')
|
8
|
+
@resource = Aws::S3::Resource.new(client: @s3)
|
9
|
+
@bucket = @resource.create_bucket(bucket: 'v2_bucket')
|
10
|
+
|
11
|
+
# Delete all objects to avoid sharing state between tests
|
12
|
+
@bucket.objects.each(&:delete)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create_bucket
|
16
|
+
bucket = @resource.create_bucket(bucket: 'v2_create_bucket')
|
17
|
+
assert_not_nil bucket
|
18
|
+
|
19
|
+
bucket_names = @resource.buckets.map(&:name)
|
20
|
+
assert(bucket_names.index("v2_create_bucket") >= 0)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_destroy_bucket
|
24
|
+
@bucket.delete
|
25
|
+
|
26
|
+
begin
|
27
|
+
@s3.head_bucket(bucket: 'v2_bucket')
|
28
|
+
assert_fail("Shouldn't succeed here")
|
29
|
+
rescue
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_create_object
|
34
|
+
object = @bucket.object('key')
|
35
|
+
object.put(body: 'test')
|
36
|
+
|
37
|
+
assert_equal 'test', object.get.body.string
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_delete_object
|
41
|
+
object = @bucket.object('exists')
|
42
|
+
object.put(body: 'test')
|
43
|
+
|
44
|
+
assert_equal 'test', object.get.body.string
|
45
|
+
|
46
|
+
object.delete
|
47
|
+
|
48
|
+
assert_raise Aws::S3::Errors::NoSuchKey do
|
49
|
+
object.get
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# TODO - get this test working
|
54
|
+
#
|
55
|
+
#def test_copy_object
|
56
|
+
# object = @bucket.object("key_one")
|
57
|
+
# object.put(body: 'asdf')
|
58
|
+
|
59
|
+
# # TODO: explore why 'key1' won't work but 'key_one' will
|
60
|
+
# object2 = @bucket.object('key_two')
|
61
|
+
# object2.copy_from(copy_source: 'testing_copy/key_one')
|
62
|
+
|
63
|
+
# assert_equal 2, @bucket.objects.count
|
64
|
+
#end
|
65
|
+
end
|
data/test/boto_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class BotoTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
cmdpath = File.expand_path(File.join(File.dirname(__FILE__),'botocmd.py'))
|
7
|
+
@botocmd = "python #{cmdpath} -t localhost -p 10453"
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_store
|
14
|
+
File.open(__FILE__,'rb') do |input|
|
15
|
+
File.open("/tmp/fakes3_upload",'wb') do |output|
|
16
|
+
output << input.read
|
17
|
+
end
|
18
|
+
end
|
19
|
+
output = `#{@botocmd} put /tmp/fakes3_upload s3://s3cmd_bucket/upload`
|
20
|
+
assert_match(/stored/,output)
|
21
|
+
|
22
|
+
FileUtils.rm("/tmp/fakes3_upload")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/test/botocmd.py
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# fakes3cmd.py -- an s3cmd-like script that accepts a custom host and portname
|
4
|
+
import re
|
5
|
+
import os
|
6
|
+
from optparse import OptionParser
|
7
|
+
|
8
|
+
try:
|
9
|
+
from boto.s3.connection import S3Connection, OrdinaryCallingFormat
|
10
|
+
from boto.s3.key import Key
|
11
|
+
except ImportError:
|
12
|
+
raise Exception('You must install the boto package for python')
|
13
|
+
|
14
|
+
|
15
|
+
class FakeS3Cmd(object):
|
16
|
+
COMMANDS = ['mb', 'rb', 'put', ]
|
17
|
+
def __init__(self, host, port):
|
18
|
+
self.host = host
|
19
|
+
self.port = port
|
20
|
+
self.conn = None
|
21
|
+
self._connect()
|
22
|
+
|
23
|
+
def _connect(self):
|
24
|
+
print 'Connecting: %s:%s' % (self.host, self.port)
|
25
|
+
self.conn = S3Connection(is_secure=False,
|
26
|
+
calling_format=OrdinaryCallingFormat(),
|
27
|
+
aws_access_key_id='',
|
28
|
+
aws_secret_access_key='',
|
29
|
+
port=self.port, host=self.host)
|
30
|
+
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def _parse_uri(path):
|
34
|
+
match = re.match(r's3://([^/]+)(?:/(.*))?', path, re.I)
|
35
|
+
## (bucket, key)
|
36
|
+
return match.groups()
|
37
|
+
|
38
|
+
def mb(self, path, *args):
|
39
|
+
if not self.conn:
|
40
|
+
self._connect()
|
41
|
+
|
42
|
+
bucket, _ = self._parse_uri(path)
|
43
|
+
self.conn.create_bucket(bucket)
|
44
|
+
print 'made bucket: [%s]' % bucket
|
45
|
+
|
46
|
+
def rb(self, path, *args):
|
47
|
+
if not self.conn:
|
48
|
+
self._connect()
|
49
|
+
|
50
|
+
bucket, _ = self._parse_uri(path)
|
51
|
+
self.conn.delete_bucket(bucket)
|
52
|
+
print 'removed bucket: [%s]' % bucket
|
53
|
+
|
54
|
+
def put(self, *args):
|
55
|
+
if not self.conn:
|
56
|
+
self._connect()
|
57
|
+
|
58
|
+
args = list(args)
|
59
|
+
path = args.pop()
|
60
|
+
bucket_name, prefix = self._parse_uri(path)
|
61
|
+
bucket = self.conn.create_bucket(bucket_name)
|
62
|
+
for src_file in args:
|
63
|
+
key = Key(bucket)
|
64
|
+
key.key = os.path.join(prefix, os.path.basename(src_file))
|
65
|
+
key.set_contents_from_filename(src_file)
|
66
|
+
print 'stored: [%s]' % key.key
|
67
|
+
|
68
|
+
|
69
|
+
if __name__ == "__main__":
|
70
|
+
# check for options. TODO: This requires a more verbose help message
|
71
|
+
# to explain how the positional arguments work.
|
72
|
+
parser = OptionParser()
|
73
|
+
parser.add_option("-t", "--host", type="string", default='localhost')
|
74
|
+
parser.add_option("-p", "--port", type='int', default=80)
|
75
|
+
o, args = parser.parse_args()
|
76
|
+
|
77
|
+
if len(args) < 2:
|
78
|
+
raise ValueError('you must minimally supply a desired command and s3 uri')
|
79
|
+
|
80
|
+
cmd = args.pop(0)
|
81
|
+
|
82
|
+
if cmd not in FakeS3Cmd.COMMANDS:
|
83
|
+
raise ValueError('%s is not a valid command' % cmd)
|
84
|
+
|
85
|
+
fs3 = FakeS3Cmd(o.host, o.port)
|
86
|
+
handler = getattr(fs3, cmd)
|
87
|
+
handler(*args)
|
data/test/cli_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'test/minitest_helper'
|
3
|
+
require 'fakes3/cli'
|
4
|
+
|
5
|
+
|
6
|
+
class CLITest < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
super
|
9
|
+
FakeS3::Server.any_instance.stubs(:serve)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_quiet_mode
|
13
|
+
script = FakeS3::CLI.new([], :root => '.', :port => 4567, :quiet => true)
|
14
|
+
assert_output('') do
|
15
|
+
script.invoke(:server)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/test/local_s3_cfg
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
[default]
|
2
|
+
access_key = abc
|
3
|
+
acl_public = False
|
4
|
+
bucket_location = US
|
5
|
+
cloudfront_host = cloudfront.amazonaws.com
|
6
|
+
cloudfront_resource = /2008-06-30/distribution
|
7
|
+
default_mime_type = binary/octet-stream
|
8
|
+
delete_removed = False
|
9
|
+
dry_run = False
|
10
|
+
encoding = UTF-8
|
11
|
+
encrypt = False
|
12
|
+
force = False
|
13
|
+
get_continue = False
|
14
|
+
gpg_command = None
|
15
|
+
gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
|
16
|
+
gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
|
17
|
+
gpg_passphrase =
|
18
|
+
guess_mime_type = True
|
19
|
+
host_base = localhost:10453
|
20
|
+
host_bucket = %(bucket)s.localhost:10453
|
21
|
+
human_readable_sizes = False
|
22
|
+
list_md5 = False
|
23
|
+
preserve_attrs = True
|
24
|
+
progress_meter = True
|
25
|
+
proxy_host =
|
26
|
+
proxy_port = 0
|
27
|
+
recursive = False
|
28
|
+
recv_chunk = 4096
|
29
|
+
secret_key = def
|
30
|
+
send_chunk = 4096
|
31
|
+
simpledb_host = sdb.amazonaws.com
|
32
|
+
skip_existing = False
|
33
|
+
use_https = False
|
34
|
+
verbosity = WARNING
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# LICENSE:
|
2
|
+
#
|
3
|
+
# (The MIT License)
|
4
|
+
#
|
5
|
+
# Copyright © Ryan Davis, seattle.rb
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
#
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
10
|
+
#
|
11
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
12
|
+
|
13
|
+
# The following is from minitest:
|
14
|
+
# TODO - decide whether to switch to minitest or what to do about these:
|
15
|
+
|
16
|
+
def capture_io
|
17
|
+
require 'stringio'
|
18
|
+
|
19
|
+
captured_stdout, captured_stderr = StringIO.new, StringIO.new
|
20
|
+
|
21
|
+
orig_stdout, orig_stderr = $stdout, $stderr
|
22
|
+
$stdout, $stderr = captured_stdout, captured_stderr
|
23
|
+
|
24
|
+
begin
|
25
|
+
yield
|
26
|
+
ensure
|
27
|
+
$stdout = orig_stdout
|
28
|
+
$stderr = orig_stderr
|
29
|
+
end
|
30
|
+
|
31
|
+
return captured_stdout.string, captured_stderr.string
|
32
|
+
end
|
33
|
+
|
34
|
+
def assert_output stdout = nil, stderr = nil
|
35
|
+
out, err = capture_io do
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
|
39
|
+
err_msg = Regexp === stderr ? :assert_match : :assert_equal if stderr
|
40
|
+
out_msg = Regexp === stdout ? :assert_match : :assert_equal if stdout
|
41
|
+
|
42
|
+
y = send err_msg, stderr, err, "In stderr" if err_msg
|
43
|
+
x = send out_msg, stdout, out, "In stdout" if out_msg
|
44
|
+
|
45
|
+
(!stdout || x) && (!stderr || y)
|
46
|
+
end
|
data/test/post_test.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'rest-client'
|
3
|
+
|
4
|
+
class PostTest < Test::Unit::TestCase
|
5
|
+
# Make sure you have a posttest.localhost in your /etc/hosts/
|
6
|
+
def setup
|
7
|
+
@url='http://posttest.localhost:10453/'
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_options
|
14
|
+
RestClient.options(@url) do |response|
|
15
|
+
assert_equal(response.code, 200)
|
16
|
+
assert_equal(response.headers[:access_control_allow_origin],"*")
|
17
|
+
assert_equal(response.headers[:access_control_allow_methods], "PUT, POST, HEAD, GET, OPTIONS")
|
18
|
+
assert_equal(response.headers[:access_control_allow_headers], "Accept, Content-Type, Authorization, Content-Length, ETag, X-CSRF-Token")
|
19
|
+
assert_equal(response.headers[:access_control_expose_headers], "ETag")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_redirect
|
24
|
+
res = RestClient.post(
|
25
|
+
@url,
|
26
|
+
'key'=>'uploads/12345/${filename}',
|
27
|
+
'success_action_redirect'=>'http://somewhere.else.com/',
|
28
|
+
'file'=>File.new(__FILE__,"rb")
|
29
|
+
) { |response|
|
30
|
+
assert_equal(response.code, 307)
|
31
|
+
assert_equal(response.headers[:location], 'http://somewhere.else.com/')
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_status_200
|
36
|
+
res = RestClient.post(
|
37
|
+
@url,
|
38
|
+
'key'=>'uploads/12345/${filename}',
|
39
|
+
'success_action_status'=>'200',
|
40
|
+
'file'=>File.new(__FILE__,"rb")
|
41
|
+
) { |response|
|
42
|
+
assert_equal(response.code, 200)
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_status_201
|
47
|
+
res = RestClient.post(
|
48
|
+
@url,
|
49
|
+
'key'=>'uploads/12345/${filename}',
|
50
|
+
'success_action_status'=>'201',
|
51
|
+
'file'=>File.new(__FILE__,"rb")
|
52
|
+
) { |response|
|
53
|
+
assert_equal(response.code, 201)
|
54
|
+
assert_match(%r{^\<\?xml.*uploads/12345/post_test\.rb}m, response.body)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|