simple_aws 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +0 -1
- data/README.md +12 -15
- data/lib/simple_aws/api.rb +9 -1
- data/lib/simple_aws/call_types/action_param.rb +1 -1
- data/lib/simple_aws/cloud_front.rb +1 -1
- data/lib/simple_aws/core/connection.rb +6 -1
- data/lib/simple_aws/core/request.rb +7 -0
- data/lib/simple_aws/core/response.rb +6 -1
- data/lib/simple_aws/dynamo_db.rb +1 -2
- data/lib/simple_aws/iam.rb +4 -0
- data/lib/simple_aws/s3.rb +28 -5
- data/samples/s3.rb +11 -2
- data/simple_aws.gemspec +1 -1
- data/test/simple_aws/api_test.rb +14 -0
- data/test/simple_aws/core/connection_test.rb +15 -1
- data/test/simple_aws/core/request_test.rb +16 -0
- data/test/simple_aws/core/response_test.rb +13 -0
- data/test/simple_aws/iam_test.rb +6 -0
- data/test/simple_aws/s3_test.rb +21 -1
- metadata +3 -3
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,26 +1,24 @@
|
|
1
|
-
SimpleAWS [![Travis CI Build Status](https://secure.travis-ci.org/
|
1
|
+
SimpleAWS [![Travis CI Build Status](https://secure.travis-ci.org/jasonroelofs/simple_aws.png)](http://travis-ci.org/jasonroelofs/simple_aws)
|
2
2
|
=========
|
3
3
|
|
4
4
|
A thin, simple, forward compatible Ruby wrapper around the various Amazon Web Service APIs.
|
5
5
|
|
6
|
-
|
6
|
+
Why?
|
7
7
|
-----------
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
While there are a number of well used libraries, I feel they have all fallen prey to the same two problems: they are far too complex for what they are and they all lack forward compatibility.
|
9
|
+
I've used almost all of the various major AWS Ruby libraries out there at one point or another, and ever one of them has left me wanting. Each time I pick a different AWS library, I end up in the same situation: the library itself is too complex with tons of abstraction, and when Amazon updates their API, it's a lot of work to update the library to include those new changes. What if there was a library that didn't suffer from either of these issues?
|
12
10
|
|
13
11
|
### Complexity
|
14
12
|
|
15
|
-
Every Ruby AWS library
|
13
|
+
Every Ruby AWS library I've used is simply too complex and has ended up hurting my productivity. I often found myself diving into the library's code to figure out how to call a given AWS API. Instead of just working with Amazon's API, I end up fighting the library, constantly having to re-learn whatever abstraction said library is trying to provide. They all either hard-code parameters, have their own mapping from hash keys to AWS parameters, or wrap up an Object API around everything, leading to confusion and more lost productivity when that abstraction leaks (which all abstractions do). Software is supposed to be simple to use; it's supposed to make your life easier. I've yet to find an AWS library that does this.
|
16
14
|
|
17
15
|
### Forward Compatibility
|
18
16
|
|
19
|
-
Outside of the pervasive complexity of these libraries, what finally drove me to create this library is the complete lack of forward compatibility in all of them. Any time I wanted to use a new parameter, new action, or new API, I would need to jump into the library itself to implement the missing pieces. In normal OSS fashion, this is of course to be lauded, contributing back to libraries is what makes software better. However, in the case of API wrappers, this very quickly becomes a frustration.
|
17
|
+
Outside of the pervasive complexity of these libraries, what finally drove me to create this library is the complete lack of forward compatibility in all of them. Any time I wanted to use a new parameter, new action, or new API, I would need to jump into the library itself to implement the missing pieces. In normal OSS fashion, this is of course to be lauded, as contributing back to libraries is what makes software better. However, in the case of API wrappers, this very quickly becomes a frustration.
|
20
18
|
|
21
|
-
Amazon constantly updates
|
19
|
+
Amazon constantly updates their APIs, adding parameters and actions, and at times entire new APIs. An AWS library should work *with* the API in question, not fight against it. The only thing a hard-coded parameter list does is add confusion. When you have to figure out how AWS parameters map to library parameters, or hash keys, your productivity drops. When you have to figure out how an object is calling an AWS library, and how you're supposed to use that object, your productivity drops. Likewise when you finally realize that the library does not currently support the action, parameter, or API you're trying to use at the time, your productivity is now at a complete stop.
|
22
20
|
|
23
|
-
SimpleAWS simply says no, no more leaky abstractions and confusing APIs. Just use the names of the API methods and parameters as defined in Amazon's documentation! If a new parameter is added to the API you're using, just use it. The name SimpleAWS isn't a wish or hope, it is the core philosophy.
|
21
|
+
SimpleAWS simply says no, no more leaky abstractions and confusing APIs. Just use the names of the API methods and parameters as defined in Amazon's documentation! If a new parameter is added to the API you're using, just use it. The name SimpleAWS isn't a wish or hope, it is the core philosophy. SimpleAWS focuses on being a very thin communication layer between your Ruby code and the AWS APIs. Let SimpleAWS handle the messy communication details so your code can do what it needs to do.
|
24
22
|
|
25
23
|
|
26
24
|
Surely SimpleAWS isn't just `curl`?
|
@@ -44,9 +42,9 @@ ec2 = SimpleAWS::EC2.new key, secret
|
|
44
42
|
ec2.DescribeInstances
|
45
43
|
```
|
46
44
|
|
47
|
-
### Parameters
|
45
|
+
### Parameters and Headers
|
48
46
|
|
49
|
-
|
47
|
+
Parameters and Headers to actions are sent as defined in the official AWS documentation. They must be strings that exactly match the parameters and headers as defined for each action. For example, the "InstanceId" parameter of EC2's "DescribeInstances" action must be named "InstanceId", not :instance_id. That said, there are some Quality of Life improvements to parameter handling. The following three are equivalent:
|
50
48
|
|
51
49
|
#### Just Call It
|
52
50
|
|
@@ -148,15 +146,14 @@ SimpleAWS is built to work under all major Ruby versions:
|
|
148
146
|
* 1.9.2
|
149
147
|
* 1.9.3
|
150
148
|
* ree
|
151
|
-
* HEAD
|
152
149
|
* jruby
|
153
150
|
* rubinius
|
154
151
|
|
155
152
|
### Misc Info
|
156
153
|
|
157
|
-
Author: Jason Roelofs - [Github](https://github.com/
|
154
|
+
Author: Jason Roelofs - [Github](https://github.com/jasonroelofs) [@jasonroelofs](http://twitter.com/jasonroelofs)
|
158
155
|
|
159
|
-
Source: https://github.com/
|
156
|
+
Source: https://github.com/jasonroelofs/simple_aws
|
160
157
|
|
161
|
-
Issues: https://github.com/
|
158
|
+
Issues: https://github.com/jasonroelofs/simple_aws/issues
|
162
159
|
|
data/lib/simple_aws/api.rb
CHANGED
@@ -56,7 +56,7 @@ module SimpleAWS
|
|
56
56
|
|
57
57
|
end
|
58
58
|
|
59
|
-
attr_reader :access_key, :secret_key, :region, :version
|
59
|
+
attr_reader :access_key, :secret_key, :region, :version, :debug_to
|
60
60
|
|
61
61
|
##
|
62
62
|
# Construct a new access object for the API in question.
|
@@ -75,6 +75,14 @@ module SimpleAWS
|
|
75
75
|
@version = self.class.instance_variable_get("@version")
|
76
76
|
end
|
77
77
|
|
78
|
+
##
|
79
|
+
# Flag this API to render debugging information to an IO location.
|
80
|
+
# Default is $stdout
|
81
|
+
##
|
82
|
+
def debug!(location = $stdout)
|
83
|
+
@debug_to = location
|
84
|
+
end
|
85
|
+
|
78
86
|
##
|
79
87
|
# Get the full host name for the current API
|
80
88
|
#
|
@@ -58,6 +58,10 @@ module SimpleAWS
|
|
58
58
|
##
|
59
59
|
class Connection
|
60
60
|
|
61
|
+
def initialize(api)
|
62
|
+
@api = api
|
63
|
+
end
|
64
|
+
|
61
65
|
##
|
62
66
|
# Send an SimpleAWS::Request to AWS proper, returning an SimpleAWS::Response.
|
63
67
|
# Will raise if the request has an error
|
@@ -68,7 +72,8 @@ module SimpleAWS
|
|
68
72
|
request.uri,
|
69
73
|
:query => request.params,
|
70
74
|
:headers => request.headers,
|
71
|
-
:body => request.body
|
75
|
+
:body => request.body,
|
76
|
+
:debug_output => @api.debug_to
|
72
77
|
)
|
73
78
|
)
|
74
79
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
3
|
+
|
1
4
|
module SimpleAWS
|
2
5
|
|
3
6
|
##
|
@@ -57,6 +60,10 @@ module SimpleAWS
|
|
57
60
|
process_array key, value
|
58
61
|
when Hash
|
59
62
|
process_hash key, value
|
63
|
+
when Time
|
64
|
+
super(key, value.iso8601)
|
65
|
+
when Date
|
66
|
+
super(key, value.strftime("%Y-%m-%d"))
|
60
67
|
else
|
61
68
|
super
|
62
69
|
end
|
@@ -239,7 +239,7 @@ module SimpleAWS
|
|
239
239
|
protected
|
240
240
|
|
241
241
|
def parse_and_throw_error_from(http_response)
|
242
|
-
if http_response
|
242
|
+
if has_parsed_response? http_response
|
243
243
|
error = parse_error_from http_response.parsed_response
|
244
244
|
else
|
245
245
|
error = { "Message" => http_response.response }
|
@@ -252,6 +252,11 @@ module SimpleAWS
|
|
252
252
|
)
|
253
253
|
end
|
254
254
|
|
255
|
+
def has_parsed_response?(response)
|
256
|
+
!response.parsed_response.nil? &&
|
257
|
+
response.parsed_response != ""
|
258
|
+
end
|
259
|
+
|
255
260
|
def parse_error_from(body)
|
256
261
|
if body.has_key? "ErrorResponse"
|
257
262
|
body["ErrorResponse"]["Error"]
|
data/lib/simple_aws/dynamo_db.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'simple_aws/api'
|
2
|
-
require 'simple_aws/signing/version3'
|
3
2
|
require 'simple_aws/sts'
|
4
3
|
require 'multi_json'
|
5
4
|
|
@@ -80,7 +79,7 @@ module SimpleAWS
|
|
80
79
|
"Algorithm=HmacSHA256," +
|
81
80
|
"Signature=#{build_signature_for(request)}"
|
82
81
|
|
83
|
-
connection = SimpleAWS::Connection.new
|
82
|
+
connection = SimpleAWS::Connection.new self
|
84
83
|
connection.call request
|
85
84
|
end
|
86
85
|
|
data/lib/simple_aws/iam.rb
CHANGED
data/lib/simple_aws/s3.rb
CHANGED
@@ -128,6 +128,20 @@ module SimpleAWS
|
|
128
128
|
call :get, path, options
|
129
129
|
end
|
130
130
|
|
131
|
+
##
|
132
|
+
# Send a request using HTTP POST
|
133
|
+
#
|
134
|
+
# @param path [String] The path of the resource at hand
|
135
|
+
# @param options [Hash] Options as defined above
|
136
|
+
#
|
137
|
+
# @return [SimpleAWS::Response] The results of the request
|
138
|
+
#
|
139
|
+
# @raise [SimpleAWS::UnsuccessfulResponse, SimpleAWS::UnknownErrorResponse] on response errors
|
140
|
+
##
|
141
|
+
def post(path, options = {})
|
142
|
+
call :post, path, options
|
143
|
+
end
|
144
|
+
|
131
145
|
##
|
132
146
|
# Send a request using HTTP PUT
|
133
147
|
#
|
@@ -184,7 +198,7 @@ module SimpleAWS
|
|
184
198
|
def call(method, path, options = {})
|
185
199
|
request = self.build_request method, path, options
|
186
200
|
|
187
|
-
connection = SimpleAWS::Connection.new
|
201
|
+
connection = SimpleAWS::Connection.new self
|
188
202
|
connection.call finish_and_sign_request(request)
|
189
203
|
end
|
190
204
|
|
@@ -234,10 +248,15 @@ module SimpleAWS
|
|
234
248
|
|
235
249
|
request.body = options[:body]
|
236
250
|
|
237
|
-
if request.body
|
238
|
-
request.headers["Content-
|
239
|
-
|
240
|
-
request.
|
251
|
+
if request.body
|
252
|
+
request.headers["Content-Length"] = calculate_size_of(request.body).to_s
|
253
|
+
|
254
|
+
if request.body.respond_to?(:read)
|
255
|
+
request.headers["Content-Type"] ||= "application/octet-stream"
|
256
|
+
request.headers["Expect"] = "100-continue"
|
257
|
+
end
|
258
|
+
|
259
|
+
request.headers["Content-Type"] ||= "application/x-www-form-urlencoded"
|
241
260
|
end
|
242
261
|
|
243
262
|
request
|
@@ -255,6 +274,10 @@ module SimpleAWS
|
|
255
274
|
|
256
275
|
protected
|
257
276
|
|
277
|
+
def calculate_size_of(body)
|
278
|
+
body.respond_to?(:size) ? body.size : File.size(body)
|
279
|
+
end
|
280
|
+
|
258
281
|
##
|
259
282
|
# Build and sign the final request, as per the rules here:
|
260
283
|
# http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
|
data/samples/s3.rb
CHANGED
@@ -22,6 +22,7 @@ def bad_usage
|
|
22
22
|
end
|
23
23
|
|
24
24
|
s3 = SimpleAWS::S3.new ENV["AWS_KEY"], ENV["AWS_SECRET"]
|
25
|
+
s3.debug!
|
25
26
|
|
26
27
|
bucket_name = ARGV[0]
|
27
28
|
file_name = ARGV[1]
|
@@ -36,8 +37,16 @@ puts "", "First 10 files in #{bucket_name}:", ""
|
|
36
37
|
|
37
38
|
bad_usage unless bucket_name
|
38
39
|
|
39
|
-
s3.get("/", :bucket => bucket_name, :params => {"max-keys" => 10}).
|
40
|
-
|
40
|
+
s3.get("/", :bucket => bucket_name, :params => {"max-keys" => 10}).tap do |response|
|
41
|
+
# Amazon doesn't include Contents if there are no files
|
42
|
+
# Amazon also includes just one entry if there's only one file to be found,
|
43
|
+
# where as if there's > 1 then SimpleAWS will be given a proper array.
|
44
|
+
# Gotta love XML!
|
45
|
+
if response["Contents"]
|
46
|
+
[response.contents].flatten.each do |entry|
|
47
|
+
puts entry.key
|
48
|
+
end
|
49
|
+
end
|
41
50
|
end
|
42
51
|
|
43
52
|
puts "", "Uploading #{file_name} to #{bucket_name}:", ""
|
data/simple_aws.gemspec
CHANGED
data/test/simple_aws/api_test.rb
CHANGED
@@ -49,6 +49,20 @@ describe SimpleAWS::API do
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
+
describe "#debug" do
|
53
|
+
it "allows turning on debugging for the api instance" do
|
54
|
+
obj = TestAPI.new "access_key", "secret_key"
|
55
|
+
obj.debug!
|
56
|
+
obj.debug_to.must_equal $stdout
|
57
|
+
end
|
58
|
+
|
59
|
+
it "allows changing the output location of the debugging output" do
|
60
|
+
obj = TestAPI.new "access_key", "secret_key"
|
61
|
+
obj.debug! $stderr
|
62
|
+
obj.debug_to.must_equal $stderr
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
52
66
|
describe "#region" do
|
53
67
|
it "uses default region if none given on constructor" do
|
54
68
|
TestAPI.default_region "us-west-1"
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'test_helper'
|
2
|
+
require 'simple_aws/api'
|
2
3
|
require 'simple_aws/core/request'
|
3
4
|
require 'simple_aws/core/response'
|
4
5
|
require 'simple_aws/core/connection'
|
@@ -8,7 +9,8 @@ describe SimpleAWS::Connection do
|
|
8
9
|
describe "#call" do
|
9
10
|
|
10
11
|
before do
|
11
|
-
@
|
12
|
+
@api = SimpleAWS::API.new "access", "secret"
|
13
|
+
@connection = SimpleAWS::Connection.new @api
|
12
14
|
@http_response = stub_everything(:success? => true, :parsed_response => {"value" => {}})
|
13
15
|
end
|
14
16
|
|
@@ -68,5 +70,17 @@ describe SimpleAWS::Connection do
|
|
68
70
|
@connection.call request
|
69
71
|
end
|
70
72
|
|
73
|
+
it "forwards debugging settings from the API to the backend" do
|
74
|
+
@api.debug!
|
75
|
+
|
76
|
+
request = SimpleAWS::Request.new(:get, "host.com", "/")
|
77
|
+
|
78
|
+
SimpleAWS::HTTP.expects(:get).with {|uri, options|
|
79
|
+
options[:debug_output].must_equal $stdout
|
80
|
+
}.returns(@http_response)
|
81
|
+
|
82
|
+
@connection.call request
|
83
|
+
end
|
71
84
|
end
|
85
|
+
|
72
86
|
end
|
@@ -104,4 +104,20 @@ describe SimpleAWS::Request do
|
|
104
104
|
})
|
105
105
|
end
|
106
106
|
end
|
107
|
+
|
108
|
+
describe "date / time objects" do
|
109
|
+
it "converts date objects to ISO8601 format" do
|
110
|
+
@request.params["Date"] = Date.parse("2012-02-14")
|
111
|
+
@request.params.must_equal({
|
112
|
+
"Date" => "2012-02-14"
|
113
|
+
})
|
114
|
+
end
|
115
|
+
|
116
|
+
it "converts time objects to ISO8601 format" do
|
117
|
+
@request.params["Time"] = Time.utc(2012, 04, 16, 10, 30, 25)
|
118
|
+
@request.params.must_equal({
|
119
|
+
"Time" => "2012-04-16T10:30:25Z"
|
120
|
+
})
|
121
|
+
end
|
122
|
+
end
|
107
123
|
end
|
@@ -142,6 +142,19 @@ describe SimpleAWS::Response do
|
|
142
142
|
error.code.must_equal 404
|
143
143
|
error.message.must_equal " (404): This is a response ok?"
|
144
144
|
end
|
145
|
+
|
146
|
+
it "handles errors that have an empty parsed_response" do
|
147
|
+
@http_response.stubs(:code).returns(500)
|
148
|
+
@http_response.stubs(:parsed_response).returns("")
|
149
|
+
@http_response.stubs(:response).returns("Not valid")
|
150
|
+
|
151
|
+
error = lambda {
|
152
|
+
response = SimpleAWS::Response.new @http_response
|
153
|
+
}.must_raise SimpleAWS::UnsuccessfulResponse
|
154
|
+
|
155
|
+
error.code.must_equal 500
|
156
|
+
error.message.must_equal " (500): Not valid"
|
157
|
+
end
|
145
158
|
end
|
146
159
|
|
147
160
|
describe "successful response parsing and mapping" do
|
data/test/simple_aws/iam_test.rb
CHANGED
@@ -15,6 +15,12 @@ describe SimpleAWS::IAM do
|
|
15
15
|
@api.version.must_equal "2010-05-08"
|
16
16
|
end
|
17
17
|
|
18
|
+
it "does not support region selection" do
|
19
|
+
lambda {
|
20
|
+
SimpleAWS::IAM.new "key", "secret", "us-east-1"
|
21
|
+
}.must_raise ArgumentError
|
22
|
+
end
|
23
|
+
|
18
24
|
describe "API calls" do
|
19
25
|
|
20
26
|
it "builds and signs calls with ActionParam rules" do
|
data/test/simple_aws/s3_test.rb
CHANGED
@@ -58,7 +58,7 @@ describe SimpleAWS::S3 do
|
|
58
58
|
|
59
59
|
describe "API calls" do
|
60
60
|
|
61
|
-
[:get, :put, :delete, :head].each do |method|
|
61
|
+
[:get, :post, :put, :delete, :head].each do |method|
|
62
62
|
it "supports the #{method} HTTP method" do
|
63
63
|
SimpleAWS::Connection.any_instance.expects(:call).with do |request|
|
64
64
|
request.method.must_equal method
|
@@ -141,6 +141,17 @@ describe SimpleAWS::S3 do
|
|
141
141
|
@api.get "/", :body => file
|
142
142
|
end
|
143
143
|
|
144
|
+
it "calculates size of body that isn't a File (responds to read)" do
|
145
|
+
raw_body = StringIO.new "raw data"
|
146
|
+
|
147
|
+
SimpleAWS::Connection.any_instance.expects(:call).with do |request|
|
148
|
+
request.headers["Content-Length"].must_equal "8"
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
@api.get "/", :body => raw_body
|
153
|
+
end
|
154
|
+
|
144
155
|
it "uses previously set content type if given" do
|
145
156
|
SimpleAWS::Connection.any_instance.expects(:call).with do |request|
|
146
157
|
request.headers["Content-Type"].must_equal(
|
@@ -153,6 +164,15 @@ describe SimpleAWS::S3 do
|
|
153
164
|
:headers => {"Content-Type" => "application/pdf"}
|
154
165
|
end
|
155
166
|
|
167
|
+
it "sets the default content-type on post / put if none explicitly given" do
|
168
|
+
SimpleAWS::Connection.any_instance.expects(:call).with do |request|
|
169
|
+
request.headers["Content-Type"].must_equal "application/x-www-form-urlencoded"
|
170
|
+
true
|
171
|
+
end
|
172
|
+
|
173
|
+
@api.put "/", :body => "some random body"
|
174
|
+
end
|
175
|
+
|
156
176
|
it "signs the given request according to Version 3 rules" do
|
157
177
|
SimpleAWS::Connection.any_instance.expects(:call).with do |request|
|
158
178
|
header = request.headers["Authorization"]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_aws
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -148,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
148
|
version: '0'
|
149
149
|
requirements: []
|
150
150
|
rubyforge_project:
|
151
|
-
rubygems_version: 1.8.
|
151
|
+
rubygems_version: 1.8.24
|
152
152
|
signing_key:
|
153
153
|
specification_version: 3
|
154
154
|
summary: The simplest and easiest to use AWS communication library
|