shuck 0.0.1 → 0.0.2
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/.gitignore +1 -0
- data/Gemfile.lock +8 -1
- data/Rakefile +8 -0
- data/bin/shuck +0 -2
- data/lib/shuck/bucket.rb +18 -0
- data/lib/shuck/cli.rb +10 -2
- data/lib/shuck/file_store.rb +56 -0
- data/lib/shuck/s3_object.rb +5 -0
- data/lib/shuck/server.rb +23 -11
- data/lib/shuck/version.rb +1 -1
- data/lib/shuck/xml_adapter.rb +62 -0
- data/lib/shuck.rb +1 -3
- data/shuck.gemspec +1 -0
- data/test/s3_commands_test.rb +58 -0
- data/test/test_helper.rb +8 -0
- metadata +28 -9
- data/lib/shuck/store.rb +0 -76
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shuck (0.0.
|
4
|
+
shuck (0.0.2)
|
5
5
|
builder
|
6
6
|
thor
|
7
7
|
|
@@ -9,10 +9,15 @@ GEM
|
|
9
9
|
remote: http://rubygems.org/
|
10
10
|
specs:
|
11
11
|
archive-tar-minitar (0.5.2)
|
12
|
+
aws-s3 (0.6.2)
|
13
|
+
builder
|
14
|
+
mime-types
|
15
|
+
xml-simple
|
12
16
|
builder (2.1.2)
|
13
17
|
columnize (0.3.2)
|
14
18
|
linecache19 (0.5.11)
|
15
19
|
ruby_core_source (>= 0.1.4)
|
20
|
+
mime-types (1.16)
|
16
21
|
ruby-debug-base19 (0.11.24)
|
17
22
|
columnize (>= 0.3.1)
|
18
23
|
linecache19 (>= 0.5.11)
|
@@ -24,11 +29,13 @@ GEM
|
|
24
29
|
ruby_core_source (0.1.4)
|
25
30
|
archive-tar-minitar (>= 0.5.2)
|
26
31
|
thor (0.14.4)
|
32
|
+
xml-simple (1.0.12)
|
27
33
|
|
28
34
|
PLATFORMS
|
29
35
|
ruby
|
30
36
|
|
31
37
|
DEPENDENCIES
|
38
|
+
aws-s3
|
32
39
|
builder
|
33
40
|
bundler (>= 1.0.0)
|
34
41
|
ruby-debug19
|
data/Rakefile
CHANGED
data/bin/shuck
CHANGED
data/lib/shuck/bucket.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'shuck/s3_object'
|
3
|
+
|
4
|
+
module Shuck
|
5
|
+
class Bucket
|
6
|
+
attr_accessor :name,:creation_date,:objects
|
7
|
+
|
8
|
+
def initialize(name,creation_date,objects)
|
9
|
+
@name = name
|
10
|
+
@creation_date = creation_date
|
11
|
+
@objects = []
|
12
|
+
objects.each do |obj|
|
13
|
+
@objects << obj
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/shuck/cli.rb
CHANGED
@@ -6,12 +6,20 @@ module Shuck
|
|
6
6
|
default_task("server")
|
7
7
|
|
8
8
|
desc "server", "Run a server"
|
9
|
-
method_option :root, :type => :string, :aliases => '-r'
|
9
|
+
method_option :root, :type => :string, :aliases => '-r'
|
10
10
|
method_option :port, :type => :numeric, :aliases => '-p', :required => true
|
11
11
|
def server
|
12
12
|
root = File.expand_path(options[:root])
|
13
13
|
puts "Loading Shuck with #{root} on port #{options[:port]}"
|
14
|
-
|
14
|
+
store = nil
|
15
|
+
if options[:root]
|
16
|
+
store = FileStore.new(options[:root])
|
17
|
+
end
|
18
|
+
if store.nil?
|
19
|
+
puts "You must specify a root to use a file store (the current default)"
|
20
|
+
exit(-1)
|
21
|
+
end
|
22
|
+
server = Shuck::Server.new(options[:port],store)
|
15
23
|
server.serve
|
16
24
|
end
|
17
25
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'time'
|
3
|
+
require 'shuck/bucket'
|
4
|
+
|
5
|
+
module Shuck
|
6
|
+
class FileStore
|
7
|
+
def initialize(root)
|
8
|
+
@root = root
|
9
|
+
@buckets = []
|
10
|
+
@bucket_hash = {}
|
11
|
+
Dir[File.join(root,"*")].each do |bucket|
|
12
|
+
bucket_name = File.basename(bucket)
|
13
|
+
bucket_obj = Bucket.new(bucket_name,Time.now,[])
|
14
|
+
@buckets << bucket_obj
|
15
|
+
@bucket_hash[bucket_name] = bucket_obj
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def buckets
|
20
|
+
@buckets
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_bucket(bucket)
|
24
|
+
@bucket_hash[bucket]
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_bucket(bucket)
|
28
|
+
FileUtils.mkdir_p(File.join(@root,bucket))
|
29
|
+
bucket_obj = Bucket.new(bucket,Time.now,[])
|
30
|
+
@buckets << bucket_obj
|
31
|
+
@bucket_hash[bucket] = bucket_obj
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_object(bucket,object)
|
35
|
+
begin
|
36
|
+
io = File.open(File.join(@root,bucket,object),'rb')
|
37
|
+
return io
|
38
|
+
rescue
|
39
|
+
puts $!
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def store_object(bucket,object,request)
|
45
|
+
begin
|
46
|
+
File.open(File.join(@root,bucket,object),'w') do |f|
|
47
|
+
request.body do |chunk|
|
48
|
+
f << chunk
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue
|
52
|
+
puts $!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/shuck/server.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'webrick'
|
2
|
-
require 'shuck/
|
2
|
+
require 'shuck/file_store'
|
3
|
+
require 'shuck/xml_adapter'
|
3
4
|
|
4
5
|
module Shuck
|
5
6
|
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
6
|
-
def initialize(server,
|
7
|
+
def initialize(server,store)
|
7
8
|
super(server)
|
8
|
-
@store =
|
9
|
+
@store = store
|
9
10
|
end
|
10
11
|
|
11
12
|
def do_GET(request, response)
|
@@ -15,15 +16,22 @@ module Shuck
|
|
15
16
|
if path == "/"
|
16
17
|
response.status = 200
|
17
18
|
response['Content-Type'] = 'text/xml'
|
18
|
-
|
19
|
+
buckets = @store.buckets
|
20
|
+
response.body = XmlAdapter.buckets(buckets)
|
19
21
|
else
|
20
22
|
elems = path[1,path_len].split("/")
|
21
23
|
bucket = elems[0]
|
22
24
|
if elems.size == 1
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
bucket_obj = @store.get_bucket(bucket)
|
26
|
+
if bucket_obj
|
27
|
+
response.status = 200
|
28
|
+
response.body = XmlAdapter.bucket(bucket_obj)
|
29
|
+
response['Content-Type'] = "application/xml"
|
30
|
+
else
|
31
|
+
response.status = 404
|
32
|
+
response.body = XmlAdapter.error_no_such_bucket(bucket)
|
33
|
+
response['Content-Type'] = "application/xml"
|
34
|
+
end
|
27
35
|
else
|
28
36
|
object = elems[1,elems.size].join('/')
|
29
37
|
io = @store.get_object(bucket,object)
|
@@ -72,16 +80,20 @@ module Shuck
|
|
72
80
|
end
|
73
81
|
|
74
82
|
class Server
|
75
|
-
def initialize(port,
|
83
|
+
def initialize(port,store)
|
76
84
|
@port = port
|
77
|
-
@
|
85
|
+
@store = store
|
78
86
|
end
|
79
87
|
|
80
88
|
def serve
|
81
89
|
@server = WEBrick::HTTPServer.new(:Port => @port)
|
82
|
-
@server.mount "/", Servlet, @
|
90
|
+
@server.mount "/", Servlet, @store
|
83
91
|
trap "INT" do @server.shutdown end
|
84
92
|
@server.start
|
85
93
|
end
|
94
|
+
|
95
|
+
def shutdown
|
96
|
+
@server.shutdown
|
97
|
+
end
|
86
98
|
end
|
87
99
|
end
|
data/lib/shuck/version.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Shuck
|
5
|
+
class XmlAdapter
|
6
|
+
def self.buckets(buckets)
|
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("Shuck")
|
14
|
+
}
|
15
|
+
lam.Buckets { |buckets|
|
16
|
+
@buckets.each do |bucket|
|
17
|
+
buckets.Bucket do |b|
|
18
|
+
b.Name(bucket.name)
|
19
|
+
b.CreationDate(bucket.creation_date.xmlschema)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
}
|
23
|
+
}
|
24
|
+
output
|
25
|
+
end
|
26
|
+
|
27
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
28
|
+
#<Error>
|
29
|
+
# <Code>NoSuchKey</Code>
|
30
|
+
# <Message>The resource you requested does not exist</Message>
|
31
|
+
# <Resource>/mybucket/myfoto.jpg</Resource>
|
32
|
+
# <RequestId>4442587FB7D0A2F9</RequestId>
|
33
|
+
#</Error>
|
34
|
+
def self.error_no_such_bucket(name)
|
35
|
+
output = ""
|
36
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
37
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
38
|
+
xml.Error { |err|
|
39
|
+
err.Code("NoSuchBucket")
|
40
|
+
err.Message("The resource you requested does not exist")
|
41
|
+
err.Resource(name)
|
42
|
+
err.RequestId(1)
|
43
|
+
}
|
44
|
+
output
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.bucket(bucket)
|
48
|
+
output = ""
|
49
|
+
xml = Builder::XmlMarkup.new(:target => output)
|
50
|
+
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
51
|
+
xml.ListBucketResult(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |lbr|
|
52
|
+
lbr.Name(bucket.name)
|
53
|
+
lbr.Prefix
|
54
|
+
lbr.Marker
|
55
|
+
lbr.MaxKeys("1000")
|
56
|
+
lbr.IsTruncated("false")
|
57
|
+
}
|
58
|
+
output
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/lib/shuck.rb
CHANGED
data/shuck.gemspec
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'shuck/server'
|
4
|
+
require 'aws/s3'
|
5
|
+
|
6
|
+
class S3CommandsTest < Test::Unit::TestCase
|
7
|
+
include AWS::S3
|
8
|
+
|
9
|
+
def setup
|
10
|
+
AWS::S3::Base.establish_connection!(:access_key_id => "123", :secret_access_key => "abc", :server => "localhost", :port => "10453" )
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
AWS::S3::Base.disconnect!
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_create_bucket
|
18
|
+
bucket = Bucket.create("mybucket")
|
19
|
+
assert_not_nil bucket
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_store
|
23
|
+
bucket = Bucket.create("mybucket")
|
24
|
+
S3Object.store("hello","world","mybucket")
|
25
|
+
|
26
|
+
output = ""
|
27
|
+
obj = S3Object.stream("hello","mybucket") do |chunk|
|
28
|
+
output << chunk
|
29
|
+
end
|
30
|
+
assert_equal "world", output
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_large_store
|
34
|
+
bucket = Bucket.create("mybucket")
|
35
|
+
buffer = ""
|
36
|
+
500000.times do
|
37
|
+
buffer << "#{(rand * 100).to_i}"
|
38
|
+
end
|
39
|
+
|
40
|
+
buf_len = buffer.length
|
41
|
+
S3Object.store("big",buffer,"mybucket")
|
42
|
+
|
43
|
+
output = ""
|
44
|
+
S3Object.stream("big","mybucket") do |chunk|
|
45
|
+
output << chunk
|
46
|
+
end
|
47
|
+
assert_equal buf_len,output.size
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_find_nil_bucket
|
51
|
+
begin
|
52
|
+
bucket = Bucket.find("unknown")
|
53
|
+
assert_fail "Bucket.find didn't throw an exception"
|
54
|
+
rescue
|
55
|
+
assert_equal AWS::S3::NoSuchBucket,$!.class
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
testdir = File.dirname(__FILE__)
|
5
|
+
$LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir)
|
6
|
+
|
7
|
+
libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
|
8
|
+
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Curtis Spencer
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-11-
|
17
|
+
date: 2010-11-23 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -46,7 +46,7 @@ dependencies:
|
|
46
46
|
type: :development
|
47
47
|
version_requirements: *id002
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
|
-
name:
|
49
|
+
name: aws-s3
|
50
50
|
prerelease: false
|
51
51
|
requirement: &id003 !ruby/object:Gem::Requirement
|
52
52
|
none: false
|
@@ -56,10 +56,10 @@ dependencies:
|
|
56
56
|
segments:
|
57
57
|
- 0
|
58
58
|
version: "0"
|
59
|
-
type: :
|
59
|
+
type: :development
|
60
60
|
version_requirements: *id003
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
62
|
+
name: thor
|
63
63
|
prerelease: false
|
64
64
|
requirement: &id004 !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
@@ -71,6 +71,19 @@ dependencies:
|
|
71
71
|
version: "0"
|
72
72
|
type: :runtime
|
73
73
|
version_requirements: *id004
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: builder
|
76
|
+
prerelease: false
|
77
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
type: :runtime
|
86
|
+
version_requirements: *id005
|
74
87
|
description: Use Shuck to test basic S3 functionality without actually connecting to S3
|
75
88
|
email:
|
76
89
|
- curtis@sevenforge.com
|
@@ -87,11 +100,16 @@ files:
|
|
87
100
|
- Rakefile
|
88
101
|
- bin/shuck
|
89
102
|
- lib/shuck.rb
|
103
|
+
- lib/shuck/bucket.rb
|
90
104
|
- lib/shuck/cli.rb
|
105
|
+
- lib/shuck/file_store.rb
|
106
|
+
- lib/shuck/s3_object.rb
|
91
107
|
- lib/shuck/server.rb
|
92
|
-
- lib/shuck/store.rb
|
93
108
|
- lib/shuck/version.rb
|
109
|
+
- lib/shuck/xml_adapter.rb
|
94
110
|
- shuck.gemspec
|
111
|
+
- test/s3_commands_test.rb
|
112
|
+
- test/test_helper.rb
|
95
113
|
has_rdoc: true
|
96
114
|
homepage: ""
|
97
115
|
licenses: []
|
@@ -124,5 +142,6 @@ rubygems_version: 1.3.7
|
|
124
142
|
signing_key:
|
125
143
|
specification_version: 3
|
126
144
|
summary: Shuck is a phony S3 engine with minimal dependencies
|
127
|
-
test_files:
|
128
|
-
|
145
|
+
test_files:
|
146
|
+
- test/s3_commands_test.rb
|
147
|
+
- test/test_helper.rb
|
data/lib/shuck/store.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'builder'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'time'
|
4
|
-
|
5
|
-
module Shuck
|
6
|
-
class Store
|
7
|
-
def initialize(root)
|
8
|
-
@root = root
|
9
|
-
@buckets = []
|
10
|
-
Dir[File.join(root,"*")].each do |bucket|
|
11
|
-
@buckets << File.basename(bucket)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def buckets
|
16
|
-
output = ""
|
17
|
-
xml = Builder::XmlMarkup.new(:target => output)
|
18
|
-
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
19
|
-
xml.ListAllMyBucketsResult(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |lam|
|
20
|
-
lam.Owner { |owner|
|
21
|
-
owner.ID("123")
|
22
|
-
owner.DisplayName("Curtis Spencer")
|
23
|
-
}
|
24
|
-
lam.Buckets { |buckets|
|
25
|
-
@buckets.each do |bucket|
|
26
|
-
buckets.Bucket do |b|
|
27
|
-
b.Name(bucket)
|
28
|
-
b.CreationDate(Time.now.xmlschema)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
}
|
32
|
-
}
|
33
|
-
output
|
34
|
-
end
|
35
|
-
|
36
|
-
def get_bucket(bucket)
|
37
|
-
output = ""
|
38
|
-
xml = Builder::XmlMarkup.new(:target => output)
|
39
|
-
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
40
|
-
xml.ListBucketResult(:xmlns => "http://s3.amazonaws.com/doc/2006-03-01/") { |lbr|
|
41
|
-
lbr.Name(bucket)
|
42
|
-
lbr.Prefix
|
43
|
-
lbr.Marker
|
44
|
-
lbr.MaxKeys("1000")
|
45
|
-
lbr.IsTruncated("false")
|
46
|
-
}
|
47
|
-
output
|
48
|
-
end
|
49
|
-
|
50
|
-
def create_bucket(bucket)
|
51
|
-
FileUtils.mkdir_p(File.join(@root,bucket))
|
52
|
-
end
|
53
|
-
|
54
|
-
def get_object(bucket,object)
|
55
|
-
begin
|
56
|
-
io = File.open(File.join(@root,bucket,object),'rb')
|
57
|
-
return io
|
58
|
-
rescue
|
59
|
-
puts $!
|
60
|
-
return nil
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def store_object(bucket,object,request)
|
65
|
-
begin
|
66
|
-
File.open(File.join(@root,bucket,object),'w') do |f|
|
67
|
-
request.body do |chunk|
|
68
|
-
f << chunk
|
69
|
-
end
|
70
|
-
end
|
71
|
-
rescue
|
72
|
-
puts $!
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|