simple_aws 0.0.1a
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.travis.yml +8 -0
- data/Gemfile +12 -0
- data/README.md +73 -0
- data/Rakefile +9 -0
- data/lib/aws/api.rb +82 -0
- data/lib/aws/call_types/action_param.rb +68 -0
- data/lib/aws/core/connection.rb +32 -0
- data/lib/aws/core/request.rb +133 -0
- data/lib/aws/core/response.rb +231 -0
- data/lib/aws/core/util.rb +32 -0
- data/lib/aws/ec2.rb +22 -0
- data/lib/aws/elb.rb +21 -0
- data/lib/aws/iam.rb +22 -0
- data/samples/ec2.rb +41 -0
- data/samples/elb.rb +26 -0
- data/samples/iam.rb +30 -0
- data/simple_aws.gemspec +21 -0
- data/test/aws/api_test.rb +101 -0
- data/test/aws/call_types/action_param_test.rb +52 -0
- data/test/aws/core/connection_test.rb +48 -0
- data/test/aws/core/request_test.rb +86 -0
- data/test/aws/core/response_test.rb +318 -0
- data/test/aws/ec2_test.rb +36 -0
- data/test/aws/elb_test.rb +36 -0
- data/test/aws/iam_test.rb +36 -0
- data/test/test_helper.rb +17 -0
- metadata +103 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
SimpleAWS
|
2
|
+
=========
|
3
|
+
|
4
|
+
A thin, simple, forward compatible Ruby wrapper around the various Amazon AWS API.
|
5
|
+
|
6
|
+
What? Why?!
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Another Ruby library to talk to Amazon Web Services? Why don't you just use fog
|
10
|
+
or aws or aws-sdk or right_aws or any of the myriad of others already in existence?
|
11
|
+
Why are you creating yet another one?
|
12
|
+
|
13
|
+
There's a simple, two part answer to this:
|
14
|
+
|
15
|
+
### Complexity
|
16
|
+
|
17
|
+
It's in the name. *Simple* AWS. This library focuses on being the simplest possible way
|
18
|
+
to communicate with AWS. There's been a growing trend in the Ruby Library scene to abstract
|
19
|
+
and wrap everything possible in ActiveRecord-like objects so the library user doesn't
|
20
|
+
have to worry about the details of sending requests and parsing responses from AWS.
|
21
|
+
Some make it harder than others to get to the raw requests, but once down there you
|
22
|
+
run into the other problem these libraries have.
|
23
|
+
|
24
|
+
### Forward Compatibility
|
25
|
+
|
26
|
+
Yes, not Backward, *Forward* compatibility. SimpleAWS is completely forward compatible to
|
27
|
+
any changes AWS makes to it's various APIs. It's well known that Amazon's AWS
|
28
|
+
is constantly changing, constantly being updated and added to. Unfortunately, there isn't
|
29
|
+
a library out there that lives this truth. Either parameters are hard coded, or response
|
30
|
+
values are hard coded, and for all of them the requests themselves are hard built into
|
31
|
+
the libraries, making it very hard to work with new API requests.
|
32
|
+
|
33
|
+
It's time for a library that evolves with Amazon AWS automatically and refuses to
|
34
|
+
get in your way. AWS's API is extremely well documented, consistent, and clean. The libraries
|
35
|
+
we use to interact with the API should match these truths as well.
|
36
|
+
|
37
|
+
How Simple?
|
38
|
+
-----------
|
39
|
+
|
40
|
+
Open a connection to the interface you want to talk to, and start calling methods.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
ec2 = AWS::EC2.new(key, secret)
|
44
|
+
response = ec2.describe_instances
|
45
|
+
```
|
46
|
+
|
47
|
+
If this looks familiar to other libraries, well, it's hard to get much simpler than this. Once
|
48
|
+
you move past no parameter methods though, the differences abound. What happens when Amazon
|
49
|
+
adds another parameter to DescribeInstances? SimpleAWS doesn't care, just start using it.
|
50
|
+
|
51
|
+
SimpleAWS is as light of a Ruby wrapper around the AWS APIs as possible. There are no
|
52
|
+
hard-coded parameters, no defined response types, you work directly with what the AWS
|
53
|
+
API says you get.
|
54
|
+
|
55
|
+
What SimpleAWS does do is to hide the communication complexities, the XML parsing, and
|
56
|
+
if you want to use it, some of the odd parameter systems AWS uses (PublicIp.n and the like).
|
57
|
+
On top of this, SimpleAWS works to ensure everything possible is as Ruby as possible. Methods
|
58
|
+
are underscore, the Response object can be queried using methods or a hash structure, and
|
59
|
+
parameter keys are converted to CamelCase strings as needed.
|
60
|
+
|
61
|
+
You're trying to use Amazon AWS, don't let libraries get in your way.
|
62
|
+
|
63
|
+
Project Info
|
64
|
+
------------
|
65
|
+
|
66
|
+
Author: Jason Roelofs (https://github.com/jameskilton)
|
67
|
+
|
68
|
+
Source: https://github.com/jameskilton/simple_aws
|
69
|
+
|
70
|
+
Issues: https://github.com/jameskilton/simple_aws/issues
|
71
|
+
|
72
|
+
[![Travis CI Build Status](https://secure.travis-ci.org/jameskilton/simple_aws.png)](http://travis-ci.org/jameskilton/simple_aws)
|
73
|
+
|
data/Rakefile
ADDED
data/lib/aws/api.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'aws/core/util'
|
2
|
+
require 'aws/core/connection'
|
3
|
+
require 'aws/core/request'
|
4
|
+
|
5
|
+
module AWS
|
6
|
+
|
7
|
+
##
|
8
|
+
# Base class for all endpoint handler classes.
|
9
|
+
#
|
10
|
+
# See the list of AWS Endpoints for the values to use when
|
11
|
+
# implementing various APIs:
|
12
|
+
#
|
13
|
+
# http://docs.amazonwebservices.com/general/latest/gr/index.html?rande.html
|
14
|
+
##
|
15
|
+
class API
|
16
|
+
class << self
|
17
|
+
|
18
|
+
##
|
19
|
+
# Define the AWS endpoint for the API being wrapped.
|
20
|
+
##
|
21
|
+
def endpoint(endpoint)
|
22
|
+
@endpoint = endpoint
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Specify a default region for all requests for this API.
|
27
|
+
# This region will be used if no region is given to the
|
28
|
+
# constructor
|
29
|
+
##
|
30
|
+
def default_region(region)
|
31
|
+
@default_region = region
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Specify whether this API uses HTTPS for requests. If not set,
|
36
|
+
# the system will use HTTP. Some API endpoints are not available under
|
37
|
+
# HTTP and some are only HTTP.
|
38
|
+
##
|
39
|
+
def use_https(value)
|
40
|
+
@use_https = value
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Specify the AWS version of the API in question. This will be a date string.
|
45
|
+
##
|
46
|
+
def version(version)
|
47
|
+
@version = version
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :access_key, :secret_key, :region, :version
|
53
|
+
|
54
|
+
##
|
55
|
+
# Construct a new access object for the API in question.
|
56
|
+
# +access_key+ and +secret_key+ are as defined in AWS security standards.
|
57
|
+
# Use +region+ if you need to explicitly talk to a certain AWS region
|
58
|
+
##
|
59
|
+
def initialize(access_key, secret_key, region = nil)
|
60
|
+
@access_key = access_key
|
61
|
+
@secret_key = secret_key
|
62
|
+
|
63
|
+
@region = region || self.class.instance_variable_get("@default_region")
|
64
|
+
@endpoint = self.class.instance_variable_get("@endpoint")
|
65
|
+
@use_https = self.class.instance_variable_get("@use_https")
|
66
|
+
@version = self.class.instance_variable_get("@version")
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Get the full host name for the current API
|
71
|
+
##
|
72
|
+
def uri
|
73
|
+
return @uri if @uri
|
74
|
+
|
75
|
+
@uri = @use_https ? "https" : "http"
|
76
|
+
@uri += "://#{@endpoint}"
|
77
|
+
@uri += ".#{@region}" if @region
|
78
|
+
@uri += ".amazonaws.com"
|
79
|
+
@uri
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'aws/core/util'
|
2
|
+
require 'aws/core/request'
|
3
|
+
require 'aws/core/connection'
|
4
|
+
|
5
|
+
module AWS
|
6
|
+
module CallTypes
|
7
|
+
|
8
|
+
##
|
9
|
+
# Implement call handling to work with the ?Action param, signing the message
|
10
|
+
# according to that which is defined in EC2 and ELB.
|
11
|
+
##
|
12
|
+
module ActionParam
|
13
|
+
##
|
14
|
+
# For any undefined methods, try to convert them into valid AWS
|
15
|
+
# actions and return the results
|
16
|
+
##
|
17
|
+
def method_missing(name, *args)
|
18
|
+
request = AWS::Request.new :post, self.uri, "/"
|
19
|
+
request.params["Action"] = AWS::Util.camelcase(name.to_s)
|
20
|
+
|
21
|
+
if args.any? && args.first.is_a?(Hash)
|
22
|
+
args.first.each do |key, value|
|
23
|
+
request.params[key] = value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
connection = AWS::Connection.new
|
28
|
+
connection.call finish_and_sign_request(request)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
##
|
34
|
+
# Build and sign the final request, as per the rules here:
|
35
|
+
# http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/index.html?using-query-api.html
|
36
|
+
##
|
37
|
+
def finish_and_sign_request(request)
|
38
|
+
request.params.merge!({
|
39
|
+
"AWSAccessKeyId" => self.access_key,
|
40
|
+
"SignatureMethod" => "HmacSHA256",
|
41
|
+
"SignatureVersion" => "2",
|
42
|
+
"Timestamp" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
43
|
+
"Version" => self.version
|
44
|
+
})
|
45
|
+
|
46
|
+
request.params["Signature"] = Base64.encode64(sign_request(request.params.clone)).chomp
|
47
|
+
|
48
|
+
request
|
49
|
+
end
|
50
|
+
|
51
|
+
def sign_request(params)
|
52
|
+
list = params.map {|k, v| [k, Util.uri_escape(v.to_s)] }
|
53
|
+
list.sort! do |a, b|
|
54
|
+
if a[0] == "AWSAccessKeyId"
|
55
|
+
-1
|
56
|
+
else
|
57
|
+
a[0] <=> b[0]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
host = self.uri.gsub(/^http[s]:\/\//,'')
|
62
|
+
|
63
|
+
to_sign = "POST\n#{host}\n/\n#{list.map {|p| p.join("=") }.join("&")}"
|
64
|
+
OpenSSL::HMAC.digest("sha256", self.secret_key, to_sign)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
require 'aws/core/response'
|
5
|
+
|
6
|
+
module AWS
|
7
|
+
|
8
|
+
class HTTP
|
9
|
+
include HTTParty
|
10
|
+
format :xml
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Handles all communication to and from AWS itself
|
15
|
+
##
|
16
|
+
class Connection
|
17
|
+
|
18
|
+
##
|
19
|
+
# Send an AWS::Request to AWS proper, returning an AWS::Response.
|
20
|
+
# Will raise if the request has an error
|
21
|
+
##
|
22
|
+
def call(request)
|
23
|
+
AWS::Response.new(
|
24
|
+
HTTP.send(request.method,
|
25
|
+
request.uri,
|
26
|
+
:query => request.params
|
27
|
+
)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module AWS
|
2
|
+
|
3
|
+
##
|
4
|
+
# Defines all request information needed to run a request against an AWS API
|
5
|
+
#
|
6
|
+
# Requests need to know a number of attributes to work, including the host,
|
7
|
+
# path, the HTTP method, and any params or POST bodies. Most of this is
|
8
|
+
# straight forward through the constructor and setter methods defined below.
|
9
|
+
#
|
10
|
+
# One of the more interesting aspects of the AWS API are the indexed parameters.
|
11
|
+
# These are the parameters in the document defined as such:
|
12
|
+
#
|
13
|
+
# Filter.n.Name
|
14
|
+
# Filter.n.Value.m
|
15
|
+
#
|
16
|
+
# This class has special handling to facilitate building these parameters
|
17
|
+
# from regular Ruby Hashes and Arrays, but does not prevent you from specifying
|
18
|
+
# these parameters exactly as defined. For the example above, here are the two
|
19
|
+
# ways you can set these parameters:
|
20
|
+
#
|
21
|
+
# By yourself, filling in the +n+ and +m+ as you need:
|
22
|
+
#
|
23
|
+
# request.params.merge({
|
24
|
+
# "Filter.1.Name" => "domain",
|
25
|
+
# "Filter.1.Value" => "vpc",
|
26
|
+
# "Filter.2.Name" => "ids",
|
27
|
+
# "Filter.2.Value.1" => "i-1234",
|
28
|
+
# "Filter.2.Value.2" => "i-8902"
|
29
|
+
# })
|
30
|
+
#
|
31
|
+
# Or let Request handle the indexing and numbering for you:
|
32
|
+
#
|
33
|
+
# request.params["Filter"] = [
|
34
|
+
# {"Name" => "domain", "Value" => "vpc"},
|
35
|
+
# {"Name" => "ids", "Value" => ["i-1234", "i-8902"]}
|
36
|
+
# ]
|
37
|
+
#
|
38
|
+
# If you have just a single Filter, you don't need to wrap it in an array,
|
39
|
+
# Request will do that for you:
|
40
|
+
#
|
41
|
+
# request.params["Filter"] = {"Name" => "domain", "Value" => "vpc"}
|
42
|
+
#
|
43
|
+
# Straight arrays are handled as well:
|
44
|
+
#
|
45
|
+
# request.params["InstanceId"] = ["i-1234", "i-8970"]
|
46
|
+
#
|
47
|
+
# In an effort to make this library as transparent as possible when working
|
48
|
+
# directly with the AWS API, the keys of the hashes must be the values
|
49
|
+
# specified in the API, and the values must be Hashes, Arrays, or serializable
|
50
|
+
# values like Fixnum, Boolean, or String.
|
51
|
+
#
|
52
|
+
# A more detailed example can be found in test/aws/request_test.rb where you'll
|
53
|
+
# see how to use many levels of nesting to build your AWS request.
|
54
|
+
##
|
55
|
+
class Request
|
56
|
+
|
57
|
+
class Params < Hash
|
58
|
+
|
59
|
+
def []=(key, value)
|
60
|
+
case value
|
61
|
+
when Array
|
62
|
+
process_array key, value
|
63
|
+
when Hash
|
64
|
+
process_array key, [value]
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def process_array(base_key, array_in)
|
73
|
+
array_in.each_with_index do |entry, index|
|
74
|
+
entry_key = "#{base_key}.#{index + 1}"
|
75
|
+
case entry
|
76
|
+
when Hash
|
77
|
+
process_hash entry_key, entry
|
78
|
+
else
|
79
|
+
self[entry_key] = entry
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def process_hash(base_key, entry)
|
85
|
+
entry.each do |inner_key, inner_value|
|
86
|
+
full_inner_key = "#{base_key}.#{inner_key}"
|
87
|
+
case inner_value
|
88
|
+
when Array
|
89
|
+
process_array full_inner_key, inner_value
|
90
|
+
else
|
91
|
+
self[full_inner_key] = inner_value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# HTTP method this Request will use (:get, :post, :put, :delete)
|
100
|
+
##
|
101
|
+
attr_reader :method
|
102
|
+
|
103
|
+
##
|
104
|
+
# Host and Path of the URI this Request will be using
|
105
|
+
##
|
106
|
+
attr_reader :host, :path
|
107
|
+
|
108
|
+
##
|
109
|
+
# Hash of parameters to pass in this Request. See top-level
|
110
|
+
# documentation for any special handling of types
|
111
|
+
##
|
112
|
+
attr_reader :params
|
113
|
+
|
114
|
+
##
|
115
|
+
# Set up a new Request for the given +host+ and +path+ using the given
|
116
|
+
# http +method+ (:get, :post, :put, :delete).
|
117
|
+
##
|
118
|
+
def initialize(method, host, path)
|
119
|
+
@method = method
|
120
|
+
@host = host
|
121
|
+
@path = path
|
122
|
+
@params = Params.new
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Build up the full URI
|
127
|
+
##
|
128
|
+
def uri
|
129
|
+
"#{host}#{path}"
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'aws/core/util'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
|
5
|
+
class UnsuccessfulResponse < RuntimeError
|
6
|
+
attr_reader :code
|
7
|
+
attr_reader :error_type
|
8
|
+
attr_reader :error_message
|
9
|
+
|
10
|
+
def initialize(code, error_type, error_message)
|
11
|
+
super "#{error_type} (#{code}): #{error_message}"
|
12
|
+
@code = code
|
13
|
+
@error_type = error_type
|
14
|
+
@error_message = error_message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnknownErrorResponse < RuntimeError
|
19
|
+
def initialize(body)
|
20
|
+
super "Unable to parse error code from #{body.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Wrapper object for all responses from AWS. This class gives
|
26
|
+
# a lot of leeway to how you access the response object.
|
27
|
+
# You can access the response directly through it's Hash representation,
|
28
|
+
# which is a direct mapping from the raw XML returned from AWS.
|
29
|
+
#
|
30
|
+
# You can also use ruby methods. This object will convert those methods
|
31
|
+
# in ruby_standard into appropriate keys (camelCase) and look for them
|
32
|
+
# in the hash. This can be done at any depth.
|
33
|
+
#
|
34
|
+
# This class tries not to be too magical to ensure that
|
35
|
+
# it never gets in the way. All nested objects are queryable like their
|
36
|
+
# parents are, and all sets and arrays are found and accessible through
|
37
|
+
# your typical Enumerable interface.
|
38
|
+
#
|
39
|
+
# The starting point of the Response querying will vary according to the structure
|
40
|
+
# returned by the AWS API in question. For some APIs, like EC2, the response is
|
41
|
+
# a relatively flat:
|
42
|
+
#
|
43
|
+
# <DataRequestResponse>
|
44
|
+
# <requestId>...</requestId>
|
45
|
+
# <dataRequested>
|
46
|
+
# ...
|
47
|
+
# </dataRequested>
|
48
|
+
# </DataRequestResponse>
|
49
|
+
#
|
50
|
+
# In this case, your querying will start inside of <DataRequestResponse>, ala the first
|
51
|
+
# method you'll probably call is +data_requested+. For other APIs, the response
|
52
|
+
# object is a little deeper and looks like this:
|
53
|
+
#
|
54
|
+
# <DataRequestResponse>
|
55
|
+
# <DataRequestedResult>
|
56
|
+
# <DataRequested>
|
57
|
+
# ...
|
58
|
+
# </DataRequested>
|
59
|
+
# </DataRequestedResult>
|
60
|
+
# <ResponseMetadata>
|
61
|
+
# <RequestId>...</RequestId>
|
62
|
+
# </ResponseMetadata>
|
63
|
+
# </DataRequestResponse>
|
64
|
+
#
|
65
|
+
# For these response structures, your query will start inside of <DataRequestedResult>,
|
66
|
+
# ala your first method call will be +data_requested+. To get access to the request id of
|
67
|
+
# both of these structures, simply use #request_id on the base response. You'll also
|
68
|
+
# notice the case differences of the XML tags, this class tries to ensure that case doesn't
|
69
|
+
# matter when you're querying with methods. If you're using raw hash access then yes the
|
70
|
+
# case of the keys in question need to match.
|
71
|
+
#
|
72
|
+
# This class does ensure that any collection is always an Array, given that
|
73
|
+
# when AWS returns a single item in a collection, the xml -> hash parser gives a
|
74
|
+
# single hash back instead of an array. This class will also look for
|
75
|
+
# array indicators from AWS, like <item> or <member> and squash them.
|
76
|
+
#
|
77
|
+
# If AWS returns an error code, instead of getting a Response back the library
|
78
|
+
# will instead throw an UnsuccessfulResponse error with the pertinent information.
|
79
|
+
##
|
80
|
+
class Response
|
81
|
+
|
82
|
+
# Inner proxy class that handles converting ruby methods
|
83
|
+
# into keys found in the underlying Hash.
|
84
|
+
class ResponseProxy
|
85
|
+
include Enumerable
|
86
|
+
|
87
|
+
TO_SQUASH = %w(item member)
|
88
|
+
|
89
|
+
def initialize(local_root)
|
90
|
+
first_key = local_root.keys.first
|
91
|
+
if local_root.keys.length == 1 && TO_SQUASH.include?(first_key)
|
92
|
+
# Ensure squash key is ignored and it's children are always
|
93
|
+
# turned into an array.
|
94
|
+
@local_root = [local_root[first_key]].flatten.map do |entry|
|
95
|
+
ResponseProxy.new entry
|
96
|
+
end
|
97
|
+
else
|
98
|
+
@local_root = local_root
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def [](key_or_idx)
|
103
|
+
value_or_proxy @local_root[key_or_idx]
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Get all keys at the current depth of the Response object.
|
108
|
+
# This method will raise a NoMethodError if the current
|
109
|
+
# depth is an array.
|
110
|
+
##
|
111
|
+
def keys
|
112
|
+
@local_root.keys
|
113
|
+
end
|
114
|
+
|
115
|
+
def length
|
116
|
+
@local_root.length
|
117
|
+
end
|
118
|
+
|
119
|
+
def each(&block)
|
120
|
+
@local_root.each(&block)
|
121
|
+
end
|
122
|
+
|
123
|
+
def method_missing(name, *args)
|
124
|
+
if key = key_matching(name)
|
125
|
+
value_or_proxy @local_root[key]
|
126
|
+
else
|
127
|
+
super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
def key_matching(name)
|
134
|
+
return nil if @local_root.is_a? Array
|
135
|
+
|
136
|
+
lower_base_aws_name = AWS::Util.camelcase name.to_s, :lower
|
137
|
+
upper_base_aws_name = AWS::Util.camelcase name.to_s
|
138
|
+
|
139
|
+
keys = @local_root.keys
|
140
|
+
|
141
|
+
if keys.include? lower_base_aws_name
|
142
|
+
lower_base_aws_name
|
143
|
+
elsif keys.include? upper_base_aws_name
|
144
|
+
upper_base_aws_name
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def value_or_proxy(value)
|
149
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
150
|
+
ResponseProxy.new value
|
151
|
+
else
|
152
|
+
value
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# The raw parsed response body in Hash format
|
159
|
+
##
|
160
|
+
attr_reader :body
|
161
|
+
|
162
|
+
def initialize(http_response)
|
163
|
+
if !http_response.success?
|
164
|
+
error = parse_error_from http_response.parsed_response
|
165
|
+
raise UnsuccessfulResponse.new(
|
166
|
+
http_response.code,
|
167
|
+
error["Code"],
|
168
|
+
error["Message"]
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
@body = http_response.parsed_response
|
173
|
+
|
174
|
+
inner = @body[@body.keys.first]
|
175
|
+
response_root =
|
176
|
+
if result_key = inner.keys.find {|k| k =~ /Result$/}
|
177
|
+
inner[result_key]
|
178
|
+
else
|
179
|
+
inner
|
180
|
+
end
|
181
|
+
|
182
|
+
@request_root = ResponseProxy.new response_root
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Direct access to the request body's hash.
|
187
|
+
# This works on the first level down in the AWS response, bypassing
|
188
|
+
# the root element of the returned XML so you can work directly in the
|
189
|
+
# attributes that matter
|
190
|
+
##
|
191
|
+
def [](key)
|
192
|
+
@request_root[key]
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Delegate first-level method calls to the root Proxy object
|
197
|
+
##
|
198
|
+
def method_missing(name, *args)
|
199
|
+
@request_root.send(name, *args)
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Get the request ID from this response. Works on all known AWS response formats.
|
204
|
+
# Some AWS APIs don't give a request id, such as CloudFront. For responses that
|
205
|
+
# do not have a request id, this method returns nil.
|
206
|
+
##
|
207
|
+
def request_id
|
208
|
+
if metadata = @body[@body.keys.first]["ResponseMetadata"]
|
209
|
+
metadata["RequestId"]
|
210
|
+
elsif id = @body[@body.keys.first]["requestId"]
|
211
|
+
id
|
212
|
+
else
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
protected
|
218
|
+
|
219
|
+
def parse_error_from(body)
|
220
|
+
if body.has_key? "ErrorResponse"
|
221
|
+
body["ErrorResponse"]["Error"]
|
222
|
+
elsif body.has_key? "Response"
|
223
|
+
body["Response"]["Errors"]["Error"]
|
224
|
+
else
|
225
|
+
raise UnknownErrorResponse.new body
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AWS
|
2
|
+
##
|
3
|
+
# Collection of helper methods used in the library
|
4
|
+
##
|
5
|
+
module Util
|
6
|
+
|
7
|
+
##
|
8
|
+
# Simpler version of ActiveSupport's camelize
|
9
|
+
##
|
10
|
+
def self.camelcase(string, lower_first_char = false)
|
11
|
+
if lower_first_char
|
12
|
+
string[0,1].downcase + camelcase(string)[1..-1]
|
13
|
+
else
|
14
|
+
string.split(/_/).map{ |word| word.capitalize }.join('')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# AWS URI escaping, as implemented by Fog
|
20
|
+
def self.uri_escape(string)
|
21
|
+
# Quick hack for already escaped string, don't escape again
|
22
|
+
# I don't think any requests require a % in a parameter, but if
|
23
|
+
# there is one I'll need to rethink this
|
24
|
+
return string if string =~ /%/
|
25
|
+
|
26
|
+
string.gsub(/([^a-zA-Z0-9_.\-~]+)/) {
|
27
|
+
"%" + $1.unpack("H2" * $1.bytesize).join("%").upcase
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|