fake_aws 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +2 -0
- data/README.md +24 -16
- data/fake_aws.gemspec +4 -1
- data/lib/fake_aws.rb +10 -2
- data/lib/fake_aws/error.rb +16 -0
- data/lib/fake_aws/s3/bucket_on_disk.rb +26 -0
- data/lib/fake_aws/s3/error_index.rb +37 -0
- data/lib/fake_aws/s3/object_on_disk.rb +50 -0
- data/lib/fake_aws/s3/operations/get_object.rb +30 -23
- data/lib/fake_aws/s3/operations/put_bucket.rb +38 -0
- data/lib/fake_aws/s3/operations/put_object.rb +22 -36
- data/lib/fake_aws/s3/rack_app.rb +19 -6
- data/lib/fake_aws/s3/request.rb +100 -0
- data/lib/fake_aws/s3/responses/common.rb +37 -0
- data/lib/fake_aws/s3/responses/empty.rb +25 -0
- data/lib/fake_aws/s3/responses/error.rb +50 -0
- data/lib/fake_aws/s3/responses/success.rb +31 -0
- data/lib/fake_aws/version.rb +1 -1
- data/spec/integration/s3/get_object_spec.rb +35 -12
- data/spec/integration/s3/put_bucket_spec.rb +40 -0
- data/spec/integration/s3/put_object_spec.rb +12 -4
- data/spec/integration/s3/request_styles_spec.rb +54 -0
- data/spec/spec_helper.rb +1 -16
- data/spec/support/common_header_examples.rb +16 -0
- data/spec/support/s3_integration_helpers.rb +18 -0
- data/spec/support/xml_parsing_helper.rb +7 -0
- data/spec/unit/s3/bucket_on_disk_spec.rb +39 -0
- data/spec/unit/s3/error_index_spec.rb +17 -0
- data/spec/unit/s3/object_on_disk_spec.rb +82 -0
- data/spec/unit/s3/request_spec.rb +92 -0
- data/spec/unit/s3/responses/empty_spec.rb +14 -0
- data/spec/unit/s3/responses/error_spec.rb +49 -0
- data/spec/unit/s3/responses/success_spec.rb +25 -0
- metadata +82 -26
- data/lib/fake_aws/s3/object_store.rb +0 -66
- data/lib/fake_aws/s3/xml_error_response.rb +0 -29
- data/spec/unit/s3/object_store_spec.rb +0 -75
- data/spec/unit/s3/xml_error_response_spec.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcacd66d77b727c137301c6de05fa146cb88a507
|
4
|
+
data.tar.gz: 614b3b3159d8b544a7826d224bf444c35383db95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9213550b7d286db1f0f7a62efc0488aa17d637a1e0a20f08c283854dbcb8a2c4a0a9fa31e13f1b288260deaaab9ed6d42930ba604199191f2287e5b91b80f5b7
|
7
|
+
data.tar.gz: d20fed38db8168dbf9c9588c3d4ecb198547a74f503f04ce856031b85c9c17e40e8c8d8cafa752cb2dcf91b7d87ae9d4e75d3aa80585f1413492e6a7d1c01151
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -20,18 +20,11 @@ Or install it yourself as:
|
|
20
20
|
$ gem install fake_aws
|
21
21
|
|
22
22
|
|
23
|
-
## Status
|
24
|
-
|
25
|
-
This is still in the very early stages of development.
|
23
|
+
## Status [![Build Status](https://travis-ci.org/envato/fake_aws.png)](https://travis-ci.org/envato/fake_aws) [![Code Climate](https://codeclimate.com/github/envato/fake_aws.png)](https://codeclimate.com/github/envato/fake_aws)
|
26
24
|
|
27
25
|
So far there's only a tiny bit of S3 implemented, but it's well tested and
|
28
26
|
fairly easy to extend. Pull requests for more features are welcome.
|
29
27
|
|
30
|
-
The S3 implementation only supports basic PUT Object and GET Object requests.
|
31
|
-
The bucket name must be in the path, not the host. No authentication or
|
32
|
-
security is implemented. `Content-Type` and `x-amz-metadata` headers are
|
33
|
-
stored and returned. Responses may or may not be properly formatted.
|
34
|
-
|
35
28
|
|
36
29
|
## Usage
|
37
30
|
|
@@ -57,15 +50,29 @@ end
|
|
57
50
|
|
58
51
|
will create a file `root_directory/test_bucket/test_path/test_file.txt`.
|
59
52
|
|
60
|
-
(Note: `root_directory/test_bucket` must already exist! As there's no
|
61
|
-
implementation of the Create Bucket operation yet, you'll need to make the
|
62
|
-
directory for the bucket yourself before doing a PUT Object.)
|
63
|
-
|
64
53
|
It will also create
|
65
54
|
`root_directory/test_bucket/test_path/test_file.txt.metadata.json`, which holds
|
66
55
|
the metadata for the file as a JSON hash.
|
67
56
|
|
68
57
|
|
58
|
+
## Implemented Operations
|
59
|
+
|
60
|
+
### S3
|
61
|
+
|
62
|
+
Path-style, virtual-hosted-style, and CNAME-style requests are supported for
|
63
|
+
all operations.
|
64
|
+
|
65
|
+
No authentication or security is implemented.
|
66
|
+
|
67
|
+
`Content-Type` and `x-amz-metadata` headers are stored and returned.
|
68
|
+
|
69
|
+
- [GET Object](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html)
|
70
|
+
- [PUT Bucket](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html)
|
71
|
+
- [PUT Object](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html)
|
72
|
+
|
73
|
+
Pull requests for other operations are welcome!
|
74
|
+
|
75
|
+
|
69
76
|
## Contributing
|
70
77
|
|
71
78
|
1. Fork it
|
@@ -76,8 +83,9 @@ the metadata for the file as a JSON hash.
|
|
76
83
|
|
77
84
|
## To Do
|
78
85
|
|
79
|
-
-
|
80
|
-
-
|
81
|
-
- Complete the missing fields in XML error responses
|
86
|
+
- Implement [GET Bucket](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html) requests
|
87
|
+
- Improve the [response headers](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html) (Content-Length, ETag, etc.)
|
82
88
|
- Check signing of requests
|
83
|
-
- Handle PUT Object
|
89
|
+
- Handle PUT Object Copy requests
|
90
|
+
|
91
|
+
|
data/fake_aws.gemspec
CHANGED
@@ -18,9 +18,12 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "finer_struct", "~> 0.0.5"
|
22
|
+
|
21
23
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
24
|
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0.0"
|
24
26
|
spec.add_development_dependency "faraday"
|
25
27
|
spec.add_development_dependency "rack-test"
|
28
|
+
spec.add_development_dependency "nori", "~> 2.3.0"
|
26
29
|
end
|
data/lib/fake_aws.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require "fake_aws/version"
|
2
|
-
require "fake_aws/
|
2
|
+
require "fake_aws/error"
|
3
|
+
require "fake_aws/s3/bucket_on_disk"
|
4
|
+
require "fake_aws/s3/error_index"
|
5
|
+
require "fake_aws/s3/object_on_disk"
|
3
6
|
require "fake_aws/s3/rack_app"
|
4
|
-
require "fake_aws/s3/
|
7
|
+
require "fake_aws/s3/request"
|
5
8
|
require "fake_aws/s3/operations/get_object"
|
9
|
+
require "fake_aws/s3/operations/put_bucket"
|
6
10
|
require "fake_aws/s3/operations/put_object"
|
11
|
+
require "fake_aws/s3/responses/common"
|
12
|
+
require "fake_aws/s3/responses/empty"
|
13
|
+
require "fake_aws/s3/responses/error"
|
14
|
+
require "fake_aws/s3/responses/success"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module FakeAWS
|
2
|
+
class Error < RuntimeError
|
3
|
+
end
|
4
|
+
|
5
|
+
class UnknownResponseErrorCode < Error
|
6
|
+
def initialize
|
7
|
+
super("Attempting to look up an AWS error code that doesn't exist. This is probably a bug in FakeAWS.")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class UnsupportedRequestError < Error
|
12
|
+
def initialize
|
13
|
+
super("FakeAWS doesn't (yet) support this type of AWS request.")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module FakeAWS
|
2
|
+
module S3
|
3
|
+
|
4
|
+
class BucketOnDisk
|
5
|
+
|
6
|
+
def initialize(root_directory, bucket)
|
7
|
+
@root_directory = root_directory
|
8
|
+
@bucket = bucket
|
9
|
+
end
|
10
|
+
|
11
|
+
def exists?
|
12
|
+
Dir.exists?(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create
|
16
|
+
FileUtils.mkdir_p(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
@path ||= File.join(@root_directory, @bucket)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'finer_struct'
|
2
|
+
|
3
|
+
module FakeAWS
|
4
|
+
module S3
|
5
|
+
|
6
|
+
# S3 error information, extracted from:
|
7
|
+
#
|
8
|
+
# http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
9
|
+
module ErrorIndex
|
10
|
+
class Error < FinerStruct::Immutable(:description, :status_code); end
|
11
|
+
|
12
|
+
def self.error_for_code(error_code)
|
13
|
+
errors[error_code] || raise(FakeAWS::UnknownResponseErrorCode)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.errors
|
19
|
+
@errors ||= {
|
20
|
+
"NoSuchBucket" => Error.new(
|
21
|
+
:description => "The specified bucket does not exist.",
|
22
|
+
:status_code => 404
|
23
|
+
),
|
24
|
+
"NoSuchKey" => Error.new(
|
25
|
+
:description => "The specified key does not exist.",
|
26
|
+
:status_code => 404
|
27
|
+
),
|
28
|
+
"BucketAlreadyExists" => Error.new(
|
29
|
+
:description => "The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.",
|
30
|
+
:status_code => 409
|
31
|
+
)
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module FakeAWS
|
4
|
+
module S3
|
5
|
+
|
6
|
+
class ObjectOnDisk
|
7
|
+
|
8
|
+
def initialize(bucket_on_disk, key = nil)
|
9
|
+
@bucket_on_disk = bucket_on_disk
|
10
|
+
@key = key
|
11
|
+
end
|
12
|
+
|
13
|
+
def exists?
|
14
|
+
File.exists?(object_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def write(content, metadata)
|
18
|
+
FileUtils.mkdir_p(directory_path)
|
19
|
+
File.write(object_path, content)
|
20
|
+
File.write(metadata_path, metadata.to_json)
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_content
|
24
|
+
File.new(object_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_metadata
|
28
|
+
if File.exists?(metadata_path)
|
29
|
+
JSON.parse(File.read(metadata_path))
|
30
|
+
else
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def object_path
|
36
|
+
@object_path ||= File.join(@bucket_on_disk.path, @key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def metadata_path
|
40
|
+
@metadata_path ||= File.join("#{object_path}.metadata.json")
|
41
|
+
end
|
42
|
+
|
43
|
+
def directory_path
|
44
|
+
@directory_path ||= File.dirname(object_path)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -3,49 +3,56 @@ module FakeAWS
|
|
3
3
|
module Operations
|
4
4
|
|
5
5
|
class GetObject
|
6
|
-
def initialize(root_directory,
|
6
|
+
def initialize(root_directory, request)
|
7
7
|
@root_directory = root_directory
|
8
|
-
@
|
8
|
+
@request = request
|
9
9
|
end
|
10
10
|
|
11
11
|
def call
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
12
|
+
return no_such_bucket_response unless bucket_on_disk.exists?
|
13
|
+
return no_such_key_response unless object_on_disk.exists?
|
14
|
+
|
15
|
+
success_response
|
17
16
|
end
|
18
17
|
|
19
18
|
private
|
20
19
|
|
21
20
|
def success_response
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
Responses::Success.new(headers, object_on_disk.read_content)
|
22
|
+
end
|
23
|
+
|
24
|
+
def no_such_bucket_response
|
25
|
+
Responses::Error.new("NoSuchBucket", "BucketName" => @request.bucket)
|
27
26
|
end
|
28
27
|
|
29
28
|
def no_such_key_response
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
]
|
29
|
+
Responses::Error.new("NoSuchKey", "Resource" => @request.key)
|
30
|
+
end
|
31
|
+
|
32
|
+
def headers
|
33
|
+
[content_type_header, user_headers].inject(:merge)
|
36
34
|
end
|
37
35
|
|
38
|
-
def
|
39
|
-
metadata
|
36
|
+
def user_headers
|
37
|
+
metadata.select {|key, _| key.start_with?("x-amz-meta-") }
|
38
|
+
end
|
39
|
+
|
40
|
+
def content_type_header
|
41
|
+
{ "Content-Type" => metadata["Content-Type"] || "application/octet-stream" }
|
40
42
|
end
|
41
43
|
|
42
44
|
def metadata
|
43
|
-
@metadata ||=
|
45
|
+
@metadata ||= object_on_disk.read_metadata
|
44
46
|
end
|
45
47
|
|
46
|
-
def
|
47
|
-
@
|
48
|
+
def object_on_disk
|
49
|
+
@object_on_disk ||= ObjectOnDisk.new(bucket_on_disk, @request.key)
|
48
50
|
end
|
51
|
+
|
52
|
+
def bucket_on_disk
|
53
|
+
@bucket_on_disk ||= BucketOnDisk.new(@root_directory, @request.bucket)
|
54
|
+
end
|
55
|
+
|
49
56
|
end
|
50
57
|
|
51
58
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module FakeAWS
|
2
|
+
module S3
|
3
|
+
|
4
|
+
module Operations
|
5
|
+
|
6
|
+
class PutBucket
|
7
|
+
def initialize(root_directory, request)
|
8
|
+
@root_directory = root_directory
|
9
|
+
@request = request
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
return bucket_already_exists_response if bucket_on_disk.exists?
|
14
|
+
|
15
|
+
bucket_on_disk.create
|
16
|
+
success_response
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def success_response
|
22
|
+
Responses::Empty.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def bucket_already_exists_response
|
26
|
+
Responses::Error.new("BucketAlreadyExists", "BucketName" => @request.bucket)
|
27
|
+
end
|
28
|
+
|
29
|
+
def bucket_on_disk
|
30
|
+
@bucket_on_disk ||= BucketOnDisk.new(@root_directory, @request.bucket)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -1,63 +1,49 @@
|
|
1
1
|
module FakeAWS
|
2
2
|
module S3
|
3
|
+
|
3
4
|
module Operations
|
4
5
|
|
5
6
|
class PutObject
|
6
|
-
def initialize(root_directory,
|
7
|
+
def initialize(root_directory, request)
|
7
8
|
@root_directory = root_directory
|
8
|
-
@
|
9
|
+
@request = request
|
9
10
|
end
|
10
11
|
|
11
12
|
def call
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
else
|
17
|
-
no_such_bucket_response
|
18
|
-
end
|
13
|
+
return no_such_bucket_response unless bucket_on_disk.exists?
|
14
|
+
|
15
|
+
object_on_disk.write(@request.content, metadata)
|
16
|
+
success_response
|
19
17
|
end
|
20
18
|
|
21
19
|
private
|
22
20
|
|
23
21
|
def success_response
|
24
|
-
|
25
|
-
200,
|
26
|
-
{ "Content-Type" => "application/xml" },
|
27
|
-
["hello world"] # TODO: Uh huh.
|
28
|
-
]
|
22
|
+
Responses::Empty.new
|
29
23
|
end
|
30
24
|
|
31
25
|
def no_such_bucket_response
|
32
|
-
|
33
|
-
404,
|
34
|
-
{ "Content-Type" => "application/xml" },
|
35
|
-
XMLErrorResponse.new(
|
36
|
-
"NoSuchBucket",
|
37
|
-
"The specified bucket does not exist.",
|
38
|
-
"/#{object_store.bucket}"
|
39
|
-
)
|
40
|
-
]
|
26
|
+
Responses::Error.new("NoSuchBucket", "BucketName" => @request.bucket)
|
41
27
|
end
|
42
28
|
|
43
|
-
def
|
44
|
-
@
|
29
|
+
def metadata
|
30
|
+
@metadata ||= [content_type_metadata, user_metadata].inject(:merge)
|
45
31
|
end
|
46
32
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
33
|
+
def content_type_metadata
|
34
|
+
{ "Content-Type" => @request.content_type }
|
35
|
+
end
|
36
|
+
|
37
|
+
def user_metadata
|
38
|
+
@request.http_headers.select {|key, _| key.start_with?("x-amz-meta-") }
|
39
|
+
end
|
50
40
|
|
51
|
-
|
52
|
-
|
53
|
-
metadata_key = env_key.sub(/^HTTP_/, "").gsub("_", "-").downcase
|
54
|
-
metadata[metadata_key] = @env[env_key]
|
55
|
-
end
|
56
|
-
end
|
41
|
+
def object_on_disk
|
42
|
+
@object_on_disk ||= ObjectOnDisk.new(bucket_on_disk, @request.key)
|
57
43
|
end
|
58
44
|
|
59
|
-
def
|
60
|
-
@
|
45
|
+
def bucket_on_disk
|
46
|
+
@bucket_on_disk ||= BucketOnDisk.new(@root_directory, @request.bucket)
|
61
47
|
end
|
62
48
|
|
63
49
|
end
|