jeff 0.7.4 → 1.0.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.
- 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
|