jeff 0.7.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/README.md +13 -7
- data/Rakefile +1 -1
- data/jeff.gemspec +1 -1
- data/lib/jeff.rb +30 -51
- data/lib/jeff/version.rb +1 -1
- data/test/test_jeff.rb +75 -0
- metadata +6 -6
- data/spec/jeff_spec.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24de0bfe87f97a2aea2d0c061de58a2c84baadfa
|
4
|
+
data.tar.gz: 64005a0efd8e83d49e5967dc17577610c9880833
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6323103376a7887a697808d401e5e98240811854ea0a9f2b099d87569b2e789a4684bfb658d2be159b43da008894cfac31a5d58a0ed1259623f147bf00acb732
|
7
|
+
data.tar.gz: 57ec8babfe50c0d041ae0641880cbcd6573eb81d89b6264ee3cf17fdce7d273e2e12a9e3ed5cb751c18cac73cabad7827349320aa34a12082ee94e90f298768b
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,20 +1,26 @@
|
|
1
1
|
# Jeff
|
2
2
|
|
3
|
-
Jeff mixes in client behaviour for Amazon Web Services (AWS)
|
3
|
+
Jeff mixes in client behaviour for Amazon Web Services (AWS) that require
|
4
4
|
[Signature version 2 authentication][sig].
|
5
5
|
|
6
|
-
|
6
|
+
Jeff builds on [Excon][exc].
|
7
7
|
|
8
8
|
![jeff][jef]
|
9
9
|
|
10
10
|
## Usage
|
11
11
|
|
12
|
-
A
|
12
|
+
A minimal example:
|
13
13
|
|
14
14
|
```ruby
|
15
|
-
|
15
|
+
ProductsService = Struct.new(:aws_access_key_id, :aws_secret_access_key) do
|
16
16
|
include Jeff
|
17
17
|
|
18
|
+
PARSER = /Status>([^<]+)/
|
19
|
+
|
20
|
+
def self.status
|
21
|
+
new('foo', 'bar').status
|
22
|
+
end
|
23
|
+
|
18
24
|
def aws_endpoint
|
19
25
|
'https://mws.amazonservices.com/Products/2011-10-01'
|
20
26
|
end
|
@@ -22,13 +28,13 @@ Service = Struct.new(:aws_access_key_id, :aws_secret_access_key) do
|
|
22
28
|
def status
|
23
29
|
get(query: { 'Action' => 'GetServiceStatus' })
|
24
30
|
.body
|
25
|
-
.match(
|
31
|
+
.match(PARSER)
|
26
32
|
.[](1)
|
27
33
|
end
|
28
34
|
end
|
29
35
|
|
30
|
-
|
31
|
-
|
36
|
+
ProductsService.status
|
37
|
+
# => "GREEN"
|
32
38
|
```
|
33
39
|
|
34
40
|
[Vacuum][vac] and [Peddler][ped] implement Jeff.
|
data/Rakefile
CHANGED
data/jeff.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ['lib']
|
16
16
|
gem.version = Jeff::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency 'excon', '~> 0.
|
18
|
+
gem.add_dependency 'excon', '~> 0.29.0'
|
19
19
|
gem.add_development_dependency 'minitest'
|
20
20
|
gem.add_development_dependency 'rake'
|
21
21
|
|
data/lib/jeff.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# Jeff's only external dependency.
|
2
2
|
require 'excon'
|
3
3
|
|
4
4
|
# Standard library dependencies.
|
@@ -10,8 +10,6 @@ require 'jeff/version'
|
|
10
10
|
|
11
11
|
# Jeff mixes in client behaviour for Amazon Web Services (AWS) that require
|
12
12
|
# Signature version 2 authentication.
|
13
|
-
#
|
14
|
-
# It's Jeff, as in Jeff Bezos.
|
15
13
|
module Jeff
|
16
14
|
# Converts a query value to a sorted query string.
|
17
15
|
class Query
|
@@ -40,11 +38,11 @@ module Jeff
|
|
40
38
|
end
|
41
39
|
|
42
40
|
# Signs an AWS request.
|
43
|
-
class
|
41
|
+
class Signer
|
44
42
|
attr :method, :host, :path, :query_string
|
45
43
|
|
46
44
|
def initialize(method, host, path, query_string)
|
47
|
-
@method = method
|
45
|
+
@method = method.upcase
|
48
46
|
@host = host
|
49
47
|
@path = path
|
50
48
|
@query_string = query_string
|
@@ -76,7 +74,7 @@ module Jeff
|
|
76
74
|
end
|
77
75
|
end
|
78
76
|
|
79
|
-
# Because Ruby's CGI escapes
|
77
|
+
# Because Ruby's CGI escapes tilde, use a custom escape.
|
80
78
|
module Utils
|
81
79
|
UNRESERVED = /([^\w.~-]+)/
|
82
80
|
|
@@ -87,11 +85,9 @@ module Jeff
|
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
90
|
-
# Amazon recommends to include a User-Agent header with every request to
|
88
|
+
# Amazon recommends to include a User-Agent header with every request to
|
91
89
|
# identify the application, its version number, programming language, and
|
92
90
|
# host.
|
93
|
-
#
|
94
|
-
# If not happy, override.
|
95
91
|
USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
|
96
92
|
|
97
93
|
def self.included(base)
|
@@ -118,30 +114,16 @@ module Jeff
|
|
118
114
|
)
|
119
115
|
end
|
120
116
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
secret aws_secret_access_key
|
132
|
-
secret= aws_secret_access_key=
|
133
|
-
).each_slice(2) do |old, new|
|
134
|
-
if old.end_with?('=')
|
135
|
-
define_method(old) do |value|
|
136
|
-
warn "[DEPRECATION] Use #{new}"
|
137
|
-
self.send(new, value)
|
138
|
-
end
|
139
|
-
else
|
140
|
-
define_method(old) do
|
141
|
-
warn "[DEPRECATION] Use #{new}"
|
142
|
-
self.send(new)
|
143
|
-
end
|
144
|
-
end
|
117
|
+
attr_accessor :aws_endpoint
|
118
|
+
|
119
|
+
attr_writer :aws_access_key_id, :aws_secret_access_key
|
120
|
+
|
121
|
+
def aws_access_key_id
|
122
|
+
@aws_access_key_id || ENV['AWS_ACCESS_KEY_ID']
|
123
|
+
end
|
124
|
+
|
125
|
+
def aws_secret_access_key
|
126
|
+
@aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY']
|
145
127
|
end
|
146
128
|
|
147
129
|
# Generate HTTP request verb methods.
|
@@ -157,36 +139,33 @@ module Jeff
|
|
157
139
|
private
|
158
140
|
|
159
141
|
def build_options(options)
|
160
|
-
# Add
|
142
|
+
# Add Content-MD5 header if uploading a file.
|
161
143
|
if options.has_key?(:body)
|
162
144
|
md5 = Content.new(options[:body]).md5
|
163
145
|
(options[:headers] ||= {}).store('Content-MD5', md5)
|
164
146
|
end
|
165
147
|
|
166
|
-
# Build
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# Generate a signature.
|
175
|
-
signature = Request
|
176
|
-
.new(
|
177
|
-
options[:method].upcase,
|
178
|
-
connection.data[:host],
|
179
|
-
options[:path] || connection.data[:path],
|
180
|
-
query_string
|
181
|
-
)
|
148
|
+
# Build query string.
|
149
|
+
query_values = default_query_values.merge(options.fetch(:query, {}))
|
150
|
+
query_string = Query.new(query_values).to_s
|
151
|
+
|
152
|
+
# Generate signature.
|
153
|
+
signature = Signer
|
154
|
+
.new(options[:method], connection.data[:host], options[:path] || connection.data[:path], query_string)
|
182
155
|
.sign_with(aws_secret_access_key)
|
183
156
|
|
184
157
|
# Return options after appending an escaped signature to query.
|
185
158
|
options.merge(query: "#{query_string}&Signature=#{Utils.escape(signature)}")
|
186
159
|
end
|
187
160
|
|
161
|
+
def default_query_values
|
162
|
+
self.class.params.reduce({}) { |a, (k, v)|
|
163
|
+
a.update(k => (v.respond_to?(:call) ? instance_exec(&v) : v))
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
188
167
|
module ClassMethods
|
189
|
-
# Gets
|
168
|
+
# Gets/updates default request parameters.
|
190
169
|
def params(hsh = {})
|
191
170
|
(@params ||= {}).update(hsh)
|
192
171
|
end
|
data/lib/jeff/version.rb
CHANGED
data/test/test_jeff.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/pride'
|
3
|
+
require 'jeff'
|
4
|
+
|
5
|
+
Excon.defaults[:mock] = true
|
6
|
+
|
7
|
+
class TestJeff < Minitest::Test
|
8
|
+
def setup
|
9
|
+
@klass = Class.new { include Jeff }
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_delegates_unset_aws_credential_to_env_vars
|
13
|
+
key = '123456'
|
14
|
+
client = @klass.new
|
15
|
+
%w(aws_access_key_id aws_secret_access_key).each do |attr|
|
16
|
+
ENV[attr.upcase] = key
|
17
|
+
assert_equal key, client.send(attr)
|
18
|
+
ENV[attr.upcase] = nil
|
19
|
+
refute_equal key, client.send(attr)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_has_required_request_query_parameters
|
24
|
+
%w(AWSAccessKeyId SignatureMethod SignatureVersion Timestamp).each do |key|
|
25
|
+
assert @klass.params.has_key?(key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_configures_request_query_parameters
|
30
|
+
@klass.instance_eval do
|
31
|
+
params 'Foo' => 'bar'
|
32
|
+
end
|
33
|
+
assert @klass.params.has_key?('Foo')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_requires_signature
|
37
|
+
signature = Jeff::Signature.new(nil)
|
38
|
+
assert_raises(ArgumentError) { signature.sign('foo') }
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_sorts_request_query_parameters_lexicographically
|
42
|
+
query = Jeff::Query.new('A10' => 1, 'A1' => 1)
|
43
|
+
assert_equal 'A1=1&A10=1', query.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_sets_user_agent_header
|
47
|
+
client = @klass.new
|
48
|
+
client.aws_endpoint = 'http://example.com/'
|
49
|
+
refute_nil client.connection.data[:headers]['User-Agent']
|
50
|
+
end
|
51
|
+
|
52
|
+
Excon::HTTP_VERBS.each do |method|
|
53
|
+
define_method "test_makes_#{method}_request" do
|
54
|
+
Excon.stub({ }, { status: 200 })
|
55
|
+
client = @klass.new
|
56
|
+
client.aws_endpoint = 'http://example.com/'
|
57
|
+
client.aws_access_key_id = 'foo'
|
58
|
+
client.aws_secret_access_key = 'bar'
|
59
|
+
assert_equal 200, client.send(method).status
|
60
|
+
Excon.stubs.clear
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_adds_contet_md5_request_header_if_given_a_request_body
|
65
|
+
Excon.stub({ }) do |params|
|
66
|
+
{ body: params[:headers]['Content-MD5'] }
|
67
|
+
end
|
68
|
+
client = @klass.new
|
69
|
+
client.aws_endpoint = 'http://example.com/'
|
70
|
+
client.aws_access_key_id = 'foo'
|
71
|
+
client.aws_secret_access_key = 'bar'
|
72
|
+
refute_empty client.post(body: 'foo').body
|
73
|
+
Excon.stubs.clear
|
74
|
+
end
|
75
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jeff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hakan Ensari
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-11-
|
11
|
+
date: 2013-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.29.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.29.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,7 +68,7 @@ files:
|
|
68
68
|
- jeff.gemspec
|
69
69
|
- lib/jeff.rb
|
70
70
|
- lib/jeff/version.rb
|
71
|
-
-
|
71
|
+
- test/test_jeff.rb
|
72
72
|
homepage: https://github.com/papercavalier/jeff
|
73
73
|
licenses: []
|
74
74
|
metadata: {}
|
@@ -93,4 +93,4 @@ signing_key:
|
|
93
93
|
specification_version: 4
|
94
94
|
summary: An AWS client
|
95
95
|
test_files:
|
96
|
-
-
|
96
|
+
- test/test_jeff.rb
|
data/spec/jeff_spec.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'minitest/pride'
|
3
|
-
require 'jeff'
|
4
|
-
|
5
|
-
Excon.defaults[:mock] = true
|
6
|
-
|
7
|
-
describe Jeff do
|
8
|
-
before do
|
9
|
-
@klass = Class.new do
|
10
|
-
include Jeff
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'has the required request query parameters' do
|
15
|
-
%w(AWSAccessKeyId SignatureMethod SignatureVersion Timestamp)
|
16
|
-
.each { |key| assert @klass.params.has_key?(key) }
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'configures the request query parameters' do
|
20
|
-
@klass.instance_eval do
|
21
|
-
params 'Foo' => 'bar'
|
22
|
-
end
|
23
|
-
assert @klass.params.has_key?('Foo')
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'requires a signature' do
|
27
|
-
sig = Jeff::Signature.new(nil)
|
28
|
-
proc { sig.sign('foo') }.must_raise ArgumentError
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'sorts the request query parameters of the client lexicographically' do
|
32
|
-
query = Jeff::Query.new('A10' => 1, 'A1' => 1)
|
33
|
-
query.to_s.must_equal('A1=1&A10=1')
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'sets a User-Agent header for the client connection' do
|
37
|
-
client = @klass.new
|
38
|
-
client.aws_endpoint = 'http://example.com/'
|
39
|
-
client.connection.data[:headers]['User-Agent'].wont_be_nil
|
40
|
-
end
|
41
|
-
|
42
|
-
Excon::HTTP_VERBS.each do |method|
|
43
|
-
it "makes a #{method.upcase} request" do
|
44
|
-
Excon.stub({ }, { status: 200 })
|
45
|
-
client = @klass.new
|
46
|
-
client.aws_endpoint = 'http://example.com/'
|
47
|
-
client.aws_access_key_id = 'foo'
|
48
|
-
client.aws_secret_access_key = 'bar'
|
49
|
-
client.send(method).status.must_equal 200
|
50
|
-
Excon.stubs.clear
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'adds a Content-MD5 request header if there is a request body' do
|
55
|
-
Excon.stub({ }) do |params|
|
56
|
-
{ body: params[:headers]['Content-MD5'] }
|
57
|
-
end
|
58
|
-
client = @klass.new
|
59
|
-
client.aws_endpoint = 'http://example.com/'
|
60
|
-
client.aws_access_key_id = 'foo'
|
61
|
-
client.aws_secret_access_key = 'bar'
|
62
|
-
client.post(body: 'foo').body.wont_be_empty
|
63
|
-
Excon.stubs.clear
|
64
|
-
end
|
65
|
-
end
|