dynamodb 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +0 -1
- data/Rakefile +1 -1
- data/dynamodb.gemspec +2 -0
- data/lib/dynamodb.rb +0 -2
- data/lib/dynamodb/connection.rb +21 -13
- data/lib/dynamodb/request.rb +26 -91
- data/lib/dynamodb/version.rb +1 -1
- data/spec/dynamodb/request_spec.rb +22 -21
- metadata +34 -2
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/dynamodb.gemspec
CHANGED
@@ -13,8 +13,10 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
14
|
s.homepage = 'http://github.com/groupme/dynamodb'
|
15
15
|
|
16
|
+
s.add_runtime_dependency 'aws4', '0.0.1'
|
16
17
|
s.add_runtime_dependency 'multi_json', '1.3.7'
|
17
18
|
|
19
|
+
s.add_development_dependency 'rake'
|
18
20
|
s.add_development_dependency 'rspec', '2.8.0'
|
19
21
|
s.add_development_dependency 'webmock', '1.8.11'
|
20
22
|
end
|
data/lib/dynamodb.rb
CHANGED
@@ -9,8 +9,6 @@ module DynamoDB
|
|
9
9
|
class ServerError < BaseError; end
|
10
10
|
class AuthenticationError < BaseError; end
|
11
11
|
|
12
|
-
Credentials = Struct.new(:access_key_id, :secret_access_key)
|
13
|
-
|
14
12
|
require 'dynamodb/version'
|
15
13
|
require 'dynamodb/connection'
|
16
14
|
require 'dynamodb/http_handler'
|
data/lib/dynamodb/connection.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "aws4/signer"
|
2
|
+
|
1
3
|
module DynamoDB
|
2
4
|
# Establishes a connection to Amazon DynamoDB using credentials.
|
3
5
|
class Connection
|
@@ -14,42 +16,48 @@ module DynamoDB
|
|
14
16
|
# Create a connection
|
15
17
|
# uri: # default 'https://dynamodb.us-east-1.amazonaws.com/'
|
16
18
|
# timeout: # default 5 seconds
|
17
|
-
# api_version: # default
|
19
|
+
# api_version: # default
|
18
20
|
#
|
19
21
|
def initialize(opts = {})
|
20
|
-
if opts[:access_key_id] && opts[:secret_access_key]
|
21
|
-
@credentials = DynamoDB::Credentials.new(opts[:access_key_id], opts[:secret_access_key])
|
22
|
-
else
|
22
|
+
if !(opts[:access_key_id] && opts[:secret_access_key])
|
23
23
|
raise ArgumentError.new("access_key_id and secret_access_key are required")
|
24
24
|
end
|
25
25
|
|
26
|
-
@uri = URI(opts[:uri] || "https://dynamodb.us-east-1.amazonaws.com/")
|
27
26
|
set_timeout(opts[:timeout]) if opts[:timeout]
|
28
|
-
|
27
|
+
@uri = URI(opts[:uri] || "https://dynamodb.us-east-1.amazonaws.com/")
|
28
|
+
region = @uri.host.split(".", 4)[1] || "us-east-1"
|
29
29
|
@api_version = opts[:api_version] || "DynamoDB_20111205"
|
30
|
+
@signer = AWS4::Signer.new(
|
31
|
+
access_key: opts[:access_key_id],
|
32
|
+
secret_key: opts[:secret_access_key],
|
33
|
+
region: region
|
34
|
+
)
|
30
35
|
end
|
31
36
|
|
32
37
|
# Create and send a request to DynamoDB
|
33
38
|
#
|
34
39
|
# This returns either a SuccessResponse or a FailureResponse.
|
35
40
|
#
|
36
|
-
# `operation` can be any DynamoDB operation. `
|
41
|
+
# `operation` can be any DynamoDB operation. `body` is a hash that will be
|
37
42
|
# used as the request body (in JSON format). More info available at:
|
38
43
|
# http://docs.amazonwebservices.com/amazondynamodb/latest/developerguide
|
39
44
|
#
|
40
|
-
def post(operation,
|
45
|
+
def post(operation, body={})
|
41
46
|
request = DynamoDB::Request.new(
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
data: data
|
47
|
+
signer: @signer,
|
48
|
+
uri: @uri,
|
49
|
+
operation: version(operation),
|
50
|
+
body: body
|
47
51
|
)
|
48
52
|
http_handler.handle(request)
|
49
53
|
end
|
50
54
|
|
51
55
|
private
|
52
56
|
|
57
|
+
def version(op)
|
58
|
+
"#{@api_version}.#{op}"
|
59
|
+
end
|
60
|
+
|
53
61
|
def http_handler
|
54
62
|
self.class.http_handler
|
55
63
|
end
|
data/lib/dynamodb/request.rb
CHANGED
@@ -1,107 +1,42 @@
|
|
1
|
-
require "base64"
|
2
|
-
require "openssl"
|
3
|
-
|
4
1
|
module DynamoDB
|
5
2
|
class Request
|
6
|
-
|
7
|
-
|
8
|
-
Base64.encode64(
|
9
|
-
OpenSSL::HMAC.digest('sha256', key, Digest::SHA256.digest(signing_string))
|
10
|
-
).strip
|
11
|
-
end
|
12
|
-
end
|
3
|
+
RFC1123 = "%a, %d %b %Y %H:%M:%S GMT"
|
4
|
+
ISO8601 = "%Y%m%dT%H%M%SZ"
|
13
5
|
|
14
|
-
attr_reader :uri, :
|
6
|
+
attr_reader :uri, :operation, :body, :signer
|
15
7
|
|
16
8
|
def initialize(args = {})
|
17
|
-
@uri
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@api_version = args[:api_version]
|
22
|
-
@region = args[:region] || "us-east-1"
|
23
|
-
@datetime = Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
|
24
|
-
@service = "dynamodb"
|
25
|
-
end
|
26
|
-
|
27
|
-
def our_headers
|
28
|
-
{
|
29
|
-
"user-agent" => "groupme/dynamodb",
|
30
|
-
"host" => uri.host,
|
31
|
-
"content-type" => "application/x-amz-json-1.0",
|
32
|
-
"content-length" => body.size,
|
33
|
-
"x-amz-date" => datetime,
|
34
|
-
"x-amz-target" => "#{api_version}.#{operation}",
|
35
|
-
"x-amz-content-sha256" => hexdigest(body || '')
|
36
|
-
}
|
9
|
+
@uri = args[:uri]
|
10
|
+
@operation = args[:operation]
|
11
|
+
@body = MultiJson.dump(args[:body])
|
12
|
+
@signer = args[:signer]
|
37
13
|
end
|
38
14
|
|
39
15
|
def headers
|
40
|
-
@headers ||=
|
41
|
-
end
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
k_region = hmac(k_date, region)
|
59
|
-
k_service = hmac(k_region, service)
|
60
|
-
k_credentials = hmac(k_service, 'aws4_request')
|
61
|
-
hexhmac(k_credentials, string_to_sign)
|
62
|
-
end
|
63
|
-
|
64
|
-
def string_to_sign
|
65
|
-
parts = []
|
66
|
-
parts << 'AWS4-HMAC-SHA256'
|
67
|
-
parts << datetime
|
68
|
-
parts << credential_string
|
69
|
-
parts << hexdigest(canonical_request)
|
70
|
-
parts.join("\n")
|
71
|
-
end
|
72
|
-
|
73
|
-
def credential_string
|
74
|
-
parts = []
|
75
|
-
parts << datetime[0,8]
|
76
|
-
parts << region
|
77
|
-
parts << service
|
78
|
-
parts << 'aws4_request'
|
79
|
-
parts.join("/")
|
80
|
-
end
|
81
|
-
|
82
|
-
def canonical_request
|
83
|
-
parts = []
|
84
|
-
parts << "POST"
|
85
|
-
parts << uri.path
|
86
|
-
parts << uri.query
|
87
|
-
parts << our_headers.sort.map {|k, v| [k,v].join(':')}.join("\n") + "\n"
|
88
|
-
parts << "content-length;content-type;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-target"
|
89
|
-
parts << our_headers['x-amz-content-sha256']
|
90
|
-
parts.join("\n")
|
16
|
+
@headers ||= signed_headers
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def signed_headers
|
22
|
+
date = Time.now.utc
|
23
|
+
h = {
|
24
|
+
"Date" => date.strftime(RFC1123),
|
25
|
+
"User-Agent" => "groupme/dynamodb",
|
26
|
+
"Host" => uri.host,
|
27
|
+
"Content-Type" => "application/x-amz-json-1.0",
|
28
|
+
"Content-Length" => body.size.to_s,
|
29
|
+
"X-AMZ-Date" => date.strftime(ISO8601),
|
30
|
+
"X-AMZ-Target" => operation,
|
31
|
+
"X-AMZ-Content-SHA256" => hexdigest(body || '')
|
32
|
+
}
|
33
|
+
signer.sign("POST", uri, h, body)
|
91
34
|
end
|
92
35
|
|
93
|
-
def hexdigest
|
36
|
+
def hexdigest(value)
|
94
37
|
digest = Digest::SHA256.new
|
95
38
|
digest.update(value)
|
96
39
|
digest.hexdigest
|
97
40
|
end
|
98
|
-
|
99
|
-
def hmac key, value
|
100
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, value)
|
101
|
-
end
|
102
|
-
|
103
|
-
def hexhmac key, value
|
104
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha256'), key, value)
|
105
|
-
end
|
106
41
|
end
|
107
42
|
end
|
data/lib/dynamodb/version.rb
CHANGED
@@ -1,31 +1,32 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe DynamoDB::Request do
|
4
|
-
let(:uri) { URI("https://dynamodb.us-east-1.amazonaws.com/") }
|
5
|
-
let(:credentials) { DynamoDB::Credentials.new("access_key_id", "secret_access_key") }
|
6
|
-
let(:data) { {} }
|
7
|
-
let(:request) {
|
8
|
-
DynamoDB::Request.new(
|
9
|
-
uri: uri,
|
10
|
-
api_version: "DynamoDB_20111205",
|
11
|
-
credentials: credentials,
|
12
|
-
data: data,
|
13
|
-
operation: "ListTables",
|
14
|
-
)
|
15
|
-
}
|
16
|
-
|
17
4
|
it "signs the request" do
|
18
5
|
Time.stub(now: Time.parse("20130508T201304Z"))
|
19
6
|
|
7
|
+
uri = URI("https://dynamodb.us-east-1.amazonaws.com/")
|
8
|
+
signer = AWS4::Signer.new(
|
9
|
+
access_key: "access_key_id",
|
10
|
+
secret_key: "secret_access_key",
|
11
|
+
region: "us-east-1"
|
12
|
+
)
|
13
|
+
request = DynamoDB::Request.new(
|
14
|
+
uri: uri,
|
15
|
+
signer: signer,
|
16
|
+
body: {},
|
17
|
+
operation: "DynamoDB_20111205.ListTables",
|
18
|
+
)
|
19
|
+
|
20
20
|
request.headers.should == {
|
21
|
-
"
|
22
|
-
"
|
23
|
-
"
|
24
|
-
"
|
25
|
-
"
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"
|
21
|
+
"Content-Type"=>"application/x-amz-json-1.0",
|
22
|
+
"Content-Length"=>"2",
|
23
|
+
"Date"=>"Wed, 08 May 2013 20:13:04 GMT",
|
24
|
+
"User-Agent"=>"groupme/dynamodb",
|
25
|
+
"Host"=>"dynamodb.us-east-1.amazonaws.com",
|
26
|
+
"X-AMZ-Target"=>"DynamoDB_20111205.ListTables",
|
27
|
+
"X-AMZ-Date"=>"20130508T201304Z",
|
28
|
+
"X-AMZ-Content-SHA256"=>"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
29
|
+
"Authorization"=>"AWS4-HMAC-SHA256 Credential=access_key_id/20130508/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;date;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=9e551627237673b4deaa2c22fd3b3777d6d0705facd35b56b289e357c4995c46"
|
29
30
|
}
|
30
31
|
end
|
31
32
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamodb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,8 +10,24 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-05-
|
13
|
+
date: 2013-05-17 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: aws4
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - '='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.0.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - '='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 0.0.1
|
15
31
|
- !ruby/object:Gem::Dependency
|
16
32
|
name: multi_json
|
17
33
|
requirement: !ruby/object:Gem::Requirement
|
@@ -28,6 +44,22 @@ dependencies:
|
|
28
44
|
- - '='
|
29
45
|
- !ruby/object:Gem::Version
|
30
46
|
version: 1.3.7
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
31
63
|
- !ruby/object:Gem::Dependency
|
32
64
|
name: rspec
|
33
65
|
requirement: !ruby/object:Gem::Requirement
|