fake_aws 0.0.2 → 0.0.3
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.
- 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 [](https://travis-ci.org/envato/fake_aws) [](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
|