simple_aws 0.0.1a
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 +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
|
+
[](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
|