jeff 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ pkg
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.3
4
+ - jruby-18mode
5
+ - jruby-19mode
6
+ - rbx-18mode
7
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'jruby-openssl', :platform => :jruby
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Hakan Ensari
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ # Jeff
2
+
3
+ Jeff is a minimum-viable client for [Amazon Web Services (AWS) APIs][aws] that
4
+ support [Signature Version 2][sign].
5
+
6
+ ## Usage
7
+
8
+ ```ruby
9
+ client = Jeff.new 'http://some-aws-url.com/'
10
+ client << params
11
+ client << data
12
+
13
+ client.request
14
+ ```
15
+
16
+ Stream responses.
17
+
18
+ ```ruby
19
+ client << ->(chunk, remaining, total) { puts chunk }
20
+
21
+ client.request
22
+ ```
23
+
24
+ [aws]: http://aws.amazon.com/
25
+ [sign]: http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc 'Run all specs in spec directory'
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ task :default => [:spec]
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require File.expand_path('../lib/jeff/version.rb', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ['Hakan Ensari']
7
+ gem.email = ['hakan.ensari@papercavalier.com']
8
+ gem.description = %q{A minimum-viable Amazon Web Services (AWS) client}
9
+ gem.summary = %q{An AWS client}
10
+ gem.homepage = 'https://github.com/hakanensari/jeff'
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = 'jeff'
16
+ gem.require_paths = ['lib']
17
+ gem.version = Jeff::VERSION
18
+
19
+ gem.add_dependency 'excon', '~> 0.14'
20
+ gem.add_development_dependency 'rake', '~> 0.9'
21
+ gem.add_development_dependency 'rspec', '~> 2.10'
22
+ end
@@ -0,0 +1,19 @@
1
+ require 'base64'
2
+ require 'forwardable'
3
+ require 'time'
4
+
5
+ require 'excon'
6
+
7
+ require 'jeff/version'
8
+ require 'jeff/query_builder'
9
+ require 'jeff/user_agent'
10
+ require 'jeff/client'
11
+ require 'jeff/signature'
12
+
13
+ module Jeff
14
+ class << self
15
+ extend Forwardable
16
+
17
+ def_delegator Client, :new
18
+ end
19
+ end
@@ -0,0 +1,126 @@
1
+ module Jeff
2
+ # A minimum-viable Amazon Web Services (AWS) client.
3
+ class Client
4
+ include UserAgent
5
+
6
+ # Internal: Returns the String request body.
7
+ attr :body
8
+
9
+ # Internal: Returns the Proc chunked request body.
10
+ attr :chunker
11
+
12
+ # Gets/Sets the String AWS access key id.
13
+ attr_accessor :key
14
+
15
+ # Gets/Sets the String AWS secret key.
16
+ attr_accessor :secret
17
+
18
+ # Creates a new client.
19
+ #
20
+ # endpoint - A String AWS endpoint.
21
+ #
22
+ # Examples
23
+ #
24
+ # client = Jeff.new 'http://ecs.amazonaws.com/onca/xml'
25
+ #
26
+ def initialize(endpoint)
27
+ @endpoint = URI endpoint
28
+ @connection = Excon.new endpoint.to_s, :headers => {
29
+ 'User-Agent' => USER_AGENT
30
+ }
31
+
32
+ reset_request_attributes
33
+ end
34
+
35
+ # Updates the request attributes.
36
+ #
37
+ # data - A Hash of parameters or a String request body or a Proc that will
38
+ # deliver chunks of data.
39
+ #
40
+ # Returns self.
41
+ #
42
+ # Examples
43
+ #
44
+ # @client << {
45
+ # 'AssociateTag' => 'tag',
46
+ # 'Service' => 'AWSECommerceService',
47
+ # 'Version' => '2011-08-01'
48
+ # }
49
+ #
50
+ def <<(data)
51
+ case data
52
+ when Hash
53
+ @params.update data
54
+ when String
55
+ @body ||= '' << data
56
+ when Proc
57
+ @chunker = data
58
+ end
59
+
60
+ self
61
+ end
62
+
63
+ # Configures the client.
64
+ #
65
+ # Yields self.
66
+ #
67
+ # Examples
68
+ #
69
+ # client.configure do |c|
70
+ # c.key = 'key'
71
+ # c.secret = 'secret'
72
+ # end
73
+ #
74
+ def configure
75
+ yield self
76
+ end
77
+
78
+ # Returns the Hash request parameters, including required defaults.
79
+ def params
80
+ {
81
+ 'AWSAccessKeyId' => @key,
82
+ 'SignatureVersion' => '2',
83
+ 'SignatureMethod' => 'HmacSHA256',
84
+ 'Timestamp' => Time.now.utc.iso8601
85
+ }.merge @params
86
+ end
87
+
88
+ # Makes an HTTP request.
89
+ #
90
+ # Returns an Excon::Response.
91
+ def request(opts = {}, &blk)
92
+ opts.update :body => @body,
93
+ :method => action,
94
+ :query => query,
95
+ :request_block => @chunker
96
+
97
+ begin
98
+ @connection.request opts, &blk
99
+ ensure
100
+ reset_request_attributes
101
+ end
102
+ end
103
+
104
+ # Returns the String URL.
105
+ def url
106
+ [@endpoint, query].join '?'
107
+ end
108
+
109
+ private
110
+
111
+ def action
112
+ @body || @chunker ? :post : :get
113
+ end
114
+
115
+ def query
116
+ @query_builder ||= QueryBuilder.new @endpoint, @secret
117
+ @query_builder.build action, params
118
+ end
119
+
120
+ def reset_request_attributes
121
+ @body = nil
122
+ @chunker = nil
123
+ @params = {}
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,42 @@
1
+ module Jeff
2
+ class QueryBuilder
3
+ UNRESERVED = /([^\w.~-]+)/
4
+
5
+ def initialize(endpoint, secret)
6
+ @endpoint = endpoint
7
+ @secret = secret
8
+ end
9
+
10
+ def build(mth, params)
11
+ @mth = mth.to_s.upcase
12
+ @query = stringify params
13
+
14
+ "#{@query}&Signature=#{escape signature}"
15
+ end
16
+
17
+ private
18
+
19
+ def escape(val)
20
+ val.to_s.gsub(UNRESERVED) do
21
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
22
+ end
23
+ end
24
+
25
+ def signature
26
+ Signature.new @secret, string_to_sign
27
+ end
28
+
29
+ def string_to_sign
30
+ [
31
+ @mth,
32
+ @endpoint.host,
33
+ @endpoint.path,
34
+ @query
35
+ ].join "\n"
36
+ end
37
+
38
+ def stringify(hsh)
39
+ hsh.map { |k, v| "#{k}=#{ escape v }" }.sort.join '&'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module Jeff
2
+ class Signature
3
+ SHA256 = OpenSSL::Digest::SHA256.new
4
+
5
+ def initialize(secret, message)
6
+ @secret = secret
7
+ @message = message
8
+ end
9
+
10
+ def digest
11
+ OpenSSL::HMAC.digest SHA256, @secret, @message
12
+ end
13
+
14
+ def to_s
15
+ Base64.encode64(digest).chomp
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ module Jeff
2
+ module UserAgent
3
+ USER_AGENT = begin
4
+ hostname = `hostname`.chomp
5
+ engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
6
+ language = [engine, RUBY_VERSION, "p#{RUBY_PATCHLEVEL}"].join ' '
7
+
8
+ "Jeff/#{VERSION} (Language=#{language}; Host=#{hostname})"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Jeff
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ module Jeff
4
+ describe Client do
5
+ before do
6
+ @endpoint = 'http://slowapi.com/delay/0'
7
+
8
+ @client = Client.new @endpoint
9
+ @client.configure do |config|
10
+ config.key = 'key'
11
+ config.secret = 'secret'
12
+ end
13
+ end
14
+
15
+ describe '#<<' do
16
+ it 'updates the request parameters' do
17
+ @client << { 'Foo' => 1 }
18
+ @client.params.should include 'Foo'
19
+ end
20
+
21
+ it 'updates the request body' do
22
+ @client << 'foo'
23
+ @client.body.should eql 'foo'
24
+ end
25
+
26
+ it 'updates the request chunked body' do
27
+ chunker = lambda {}
28
+ @client << chunker
29
+ @client.chunker.should eql chunker
30
+ end
31
+ end
32
+
33
+ describe '#params' do
34
+ subject { @client.params }
35
+
36
+ it 'includes a key' do
37
+ should include 'AWSAccessKeyId'
38
+ end
39
+
40
+ it 'includes a signature version' do
41
+ should include 'SignatureVersion'
42
+ end
43
+
44
+ it 'includes a signature method' do
45
+ should include 'SignatureMethod'
46
+ end
47
+
48
+ it 'includes a timestamp' do
49
+ should include 'Timestamp'
50
+ end
51
+ end
52
+
53
+ describe '#url' do
54
+ subject { @client.url }
55
+
56
+ it 'includes the endpoint' do
57
+ should include @endpoint
58
+ end
59
+
60
+ it 'sorts the parameters' do
61
+ @client << { 'Z' => 1, 'A' => 1 }
62
+ should match /\?A=[^&]+/
63
+ end
64
+
65
+ it 'is signed' do
66
+ should match /Signature=[^&]+$/
67
+ end
68
+ end
69
+
70
+ describe '#request' do
71
+ context 'given no body or chunker' do
72
+ it 'makes a GET request' do
73
+ Excon.stub({ :method => :get }, { :body => 'get' })
74
+ @client.request(:mock => true).body.should eql 'get'
75
+ end
76
+ end
77
+
78
+ context 'given a body' do
79
+ it 'makes a POST request' do
80
+ Excon.stub({ :method => :post }, { :body => 'post' })
81
+ @client << 'foo'
82
+ @client.request(:mock => true).body.should eql 'post'
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Jeff
4
+ describe UserAgent do
5
+ subject { UserAgent::USER_AGENT }
6
+
7
+ it 'describes the library' do
8
+ should match /Jeff\/[\d\w.]+\s/
9
+ end
10
+
11
+ it 'describes the interpreter' do
12
+ should match /Language=(?:j?ruby|rbx)/
13
+ end
14
+
15
+ it 'describes the host' do
16
+ should match /Host=[\w\d]+/
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Jeff do
4
+ describe '.new' do
5
+ it 'delegates to Client' do
6
+ Jeff.new('http://foo').should be_a Jeff::Client
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ begin
3
+ require 'pry'
4
+ rescue LoadError
5
+ end
6
+
7
+ require 'jeff'
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jeff
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Hakan Ensari
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: excon
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.14'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.14'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.9'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.9'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.10'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.10'
62
+ description: A minimum-viable Amazon Web Services (AWS) client
63
+ email:
64
+ - hakan.ensari@papercavalier.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .travis.yml
71
+ - Gemfile
72
+ - LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - jeff.gemspec
76
+ - lib/jeff.rb
77
+ - lib/jeff/client.rb
78
+ - lib/jeff/query_builder.rb
79
+ - lib/jeff/signature.rb
80
+ - lib/jeff/user_agent.rb
81
+ - lib/jeff/version.rb
82
+ - spec/jeff/client_spec.rb
83
+ - spec/jeff/user_agent_spec.rb
84
+ - spec/jeff_spec.rb
85
+ - spec/spec_helper.rb
86
+ homepage: https://github.com/hakanensari/jeff
87
+ licenses: []
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ segments:
99
+ - 0
100
+ hash: 2544026845906331060
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ segments:
108
+ - 0
109
+ hash: 2544026845906331060
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.23
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: An AWS client
116
+ test_files:
117
+ - spec/jeff/client_spec.rb
118
+ - spec/jeff/user_agent_spec.rb
119
+ - spec/jeff_spec.rb
120
+ - spec/spec_helper.rb