dynamodb 1.1.1 → 1.2.0
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/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
|