ridley 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
@@ -1,4 +1,5 @@
1
1
  require 'open-uri'
2
+ require 'retryable'
2
3
  require 'tempfile'
3
4
 
4
5
  module Ridley
@@ -17,6 +18,12 @@ module Ridley
17
18
  attr_reader :organization
18
19
  attr_reader :client_key
19
20
  attr_reader :client_name
21
+ # @return [Integer]
22
+ # how many retries to attempt on HTTP requests
23
+ attr_reader :retries
24
+ # @return [Float]
25
+ # time to wait between retries
26
+ attr_reader :retry_interval
20
27
 
21
28
  # @param [String] server_url
22
29
  # @param [String] client_name
@@ -26,6 +33,10 @@ module Ridley
26
33
  # URI query unencoded key/value pairs
27
34
  # @option options [Hash] :headers
28
35
  # unencoded HTTP header key/value pairs
36
+ # @option options [Integer] :retries (5)
37
+ # retry requests on 5XX failures
38
+ # @option options [Float] :retry_interval (0.5)
39
+ # how often we should pause between retries
29
40
  # @option options [Hash] :request
30
41
  # request options
31
42
  # @option options [Hash] :ssl
@@ -33,14 +44,25 @@ module Ridley
33
44
  # @option options [URI, String, Hash] :proxy
34
45
  # URI, String, or Hash of HTTP proxy options
35
46
  def initialize(server_url, client_name, client_key, options = {})
36
- @client_name = client_name
37
- @client_key = client_key
47
+ options = options.reverse_merge(retries: 5, retry_interval: 0.5)
48
+ @client_name = client_name
49
+ @client_key = client_key
50
+ @retries = options[:retries]
51
+ @retry_interval = options[:retry_interval]
38
52
 
39
53
  options = options.reverse_merge(
40
54
  builder: Faraday::Builder.new { |b|
41
- b.request :chef_auth, client_name, client_key
42
- b.response :chef_response
43
55
  b.response :json
56
+ b.request :retry,
57
+ max: @retries,
58
+ interval: @retry_interval,
59
+ exceptions: [
60
+ Ridley::Errors::HTTP5XXError,
61
+ Errno::ETIMEDOUT,
62
+ Faraday::Error::TimeoutError
63
+ ]
64
+ b.response :chef_response
65
+ b.request :chef_auth, client_name, client_key
44
66
 
45
67
  b.adapter :net_http_persistent
46
68
  }
@@ -117,8 +139,10 @@ module Ridley
117
139
  local = Tempfile.new('ridley-stream')
118
140
  local.binmode
119
141
 
120
- open(target, 'rb', headers) do |remote|
121
- local.write(remote.read)
142
+ retryable(tries: retries, on: OpenURI::HTTPError, sleep: retry_interval) do
143
+ open(target, 'rb', headers) do |remote|
144
+ local.write(remote.read)
145
+ end
122
146
  end
123
147
 
124
148
  local.flush
data/lib/ridley/errors.rb CHANGED
@@ -87,18 +87,21 @@ module Ridley
87
87
  end
88
88
  end
89
89
 
90
- class HTTPBadRequest < HTTPError; register_error(400); end
91
- class HTTPUnauthorized < HTTPError; register_error(401); end
92
- class HTTPForbidden < HTTPError; register_error(403); end
93
- class HTTPNotFound < HTTPError; register_error(404); end
94
- class HTTPMethodNotAllowed < HTTPError; register_error(405); end
95
- class HTTPRequestTimeout < HTTPError; register_error(408); end
96
- class HTTPConflict < HTTPError; register_error(409); end
90
+ class HTTP4XXError < HTTPError; end
91
+ class HTTP5XXError < HTTPError; end
92
+
93
+ class HTTPBadRequest < HTTP4XXError; register_error(400); end
94
+ class HTTPUnauthorized < HTTP4XXError; register_error(401); end
95
+ class HTTPForbidden < HTTP4XXError; register_error(403); end
96
+ class HTTPNotFound < HTTP4XXError; register_error(404); end
97
+ class HTTPMethodNotAllowed < HTTP4XXError; register_error(405); end
98
+ class HTTPRequestTimeout < HTTP4XXError; register_error(408); end
99
+ class HTTPConflict < HTTP4XXError; register_error(409); end
97
100
 
98
- class HTTPInternalServerError < HTTPError; register_error(500); end
99
- class HTTPNotImplemented < HTTPError; register_error(501); end
100
- class HTTPBadGateway < HTTPError; register_error(502); end
101
- class HTTPServiceUnavailable < HTTPError; register_error(503); end
102
- class HTTPGatewayTimeout < HTTPError; register_error(504); end
101
+ class HTTPInternalServerError < HTTP5XXError; register_error(500); end
102
+ class HTTPNotImplemented < HTTP5XXError; register_error(501); end
103
+ class HTTPBadGateway < HTTP5XXError; register_error(502); end
104
+ class HTTPServiceUnavailable < HTTP5XXError; register_error(503); end
105
+ class HTTPGatewayTimeout < HTTP5XXError; register_error(504); end
103
106
  end
104
107
  end
@@ -6,14 +6,18 @@ module Ridley
6
6
  require 'ridley/middleware/parse_json'
7
7
  require 'ridley/middleware/chef_response'
8
8
  require 'ridley/middleware/chef_auth'
9
+ require 'ridley/middleware/retry'
9
10
 
10
11
  Faraday.register_middleware :request,
11
- chef_auth: -> { ChefAuth }
12
+ chef_auth: -> { Ridley::Middleware::ChefAuth }
13
+
14
+ Faraday.register_middleware :request,
15
+ retry: -> { Ridley::Middleware::Retry }
12
16
 
13
17
  Faraday.register_middleware :response,
14
- json: -> { ParseJson }
18
+ json: -> { Ridley::Middleware::ParseJson }
15
19
 
16
20
  Faraday.register_middleware :response,
17
- chef_response: -> { ChefResponse }
21
+ chef_response: -> { Ridley::Middleware::ChefResponse }
18
22
  end
19
23
  end
@@ -0,0 +1,60 @@
1
+ module Ridley
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ #
4
+ # Catches exceptions and retries each request a limited number of times.
5
+ #
6
+ # @example
7
+ #
8
+ # Faraday.new do |conn|
9
+ # conn.request :retry, max: 2, interval: 0.05, exceptions: [CustomException, Faraday::Timeout::Error]
10
+ # conn.adapter ...
11
+ # end
12
+ #
13
+ # @note Borrowed and modified from: {https://github.com/lostisland/faraday/blob/master/lib/faraday/request/retry.rb}
14
+ # use the Faraday official middleware after the release of 0.9.x
15
+ class Middleware::Retry < Faraday::Middleware
16
+ # @option options [Integer] :max
17
+ # maximum number of retries
18
+ # @option options [Float] :interval
19
+ # pause in seconds between retries
20
+ # @option options [Array] :exceptions
21
+ # the list of exceptions to handle
22
+ def initialize(app, options = {})
23
+ super(app)
24
+ @options = options.slice(:max, :interval, :exceptions)
25
+ @errmatch = build_exception_matcher(@options[:exceptions])
26
+ end
27
+
28
+ def call(env)
29
+ retries = @options[:max]
30
+ begin
31
+ @app.call(env)
32
+ rescue @errmatch
33
+ if retries > 0
34
+ retries -= 1
35
+ sleep @options[:interval] if @options[:interval] > 0
36
+ retry
37
+ end
38
+ raise
39
+ end
40
+ end
41
+
42
+ # construct an exception matcher object.
43
+ #
44
+ # An exception matcher for the rescue clause can usually be any object that
45
+ # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
46
+ def build_exception_matcher(exceptions)
47
+ matcher = Module.new
48
+ (class << matcher; self; end).class_eval do
49
+ define_method(:===) do |error|
50
+ exceptions.any? do |ex|
51
+ if ex.is_a? Module then error.is_a? ex
52
+ else error.class.to_s == ex.to_s
53
+ end
54
+ end
55
+ end
56
+ end
57
+ matcher
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module Ridley
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
data/ridley.gemspec CHANGED
@@ -32,4 +32,5 @@ Gem::Specification.new do |s|
32
32
  s.add_runtime_dependency 'net-ssh'
33
33
  s.add_runtime_dependency 'erubis'
34
34
  s.add_runtime_dependency 'net-http-persistent', '>= 2.8'
35
+ s.add_runtime_dependency 'retryable'
35
36
  end
@@ -0,0 +1,22 @@
1
+ # Borrowed and modified from: {https://github.com/opscode/chef/blob/11.4.0/spec/unit/digester_spec.rb}
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Ridley::Chef::Digester do
6
+ before(:each) do
7
+ @cache = described_class.instance
8
+ end
9
+
10
+ describe "when computing checksums of cookbook files and templates" do
11
+ it "proxies the class method checksum_for_file to the instance" do
12
+ @cache.should_receive(:checksum_for_file).with("a_file_or_a_fail")
13
+ described_class.checksum_for_file("a_file_or_a_fail")
14
+ end
15
+
16
+ it "generates a checksum from a non-file IO object" do
17
+ io = StringIO.new("riseofthemachines\nriseofthechefs\n")
18
+ expected_md5 = '0e157ac1e2dd73191b76067fb6b4bceb'
19
+ @cache.generate_md5_checksum(io).should == expected_md5
20
+ end
21
+ end
22
+ end
@@ -9,6 +9,33 @@ describe Ridley::Connection do
9
9
  described_class.new(server_url, client_name, client_key)
10
10
  end
11
11
 
12
+ describe "configurable retries" do
13
+ before(:each) do
14
+ stub_request(:get, "https://api.opscode.com/organizations/vialstudios").to_return(status: 500, body: "")
15
+ end
16
+
17
+ it "attempts five (5) retries by default" do
18
+ expect {
19
+ subject.get('organizations/vialstudios')
20
+ }.to raise_error
21
+ a_request(:get, "https://api.opscode.com/organizations/vialstudios").should have_been_made.times(6)
22
+ end
23
+
24
+ context "given a configured count of two (2) retries" do
25
+ subject do
26
+ described_class.new(server_url, client_name, client_key, retries: 2)
27
+ end
28
+
29
+ it "attempts two (2) retries" do
30
+ expect {
31
+ subject.get('organizations/vialstudios')
32
+ }.to raise_error
33
+
34
+ a_request(:get, "https://api.opscode.com/organizations/vialstudios").should have_been_made.times(3)
35
+ end
36
+ end
37
+ end
38
+
12
39
  describe "#api_type" do
13
40
  it "returns :foss if the organization is not set" do
14
41
  subject.stub(:organization).and_return(nil)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ridley
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-27 00:00:00.000000000 Z
12
+ date: 2013-02-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -251,6 +251,22 @@ dependencies:
251
251
  - - ! '>='
252
252
  - !ruby/object:Gem::Version
253
253
  version: '2.8'
254
+ - !ruby/object:Gem::Dependency
255
+ name: retryable
256
+ requirement: !ruby/object:Gem::Requirement
257
+ none: false
258
+ requirements:
259
+ - - ! '>='
260
+ - !ruby/object:Gem::Version
261
+ version: '0'
262
+ type: :runtime
263
+ prerelease: false
264
+ version_requirements: !ruby/object:Gem::Requirement
265
+ none: false
266
+ requirements:
267
+ - - ! '>='
268
+ - !ruby/object:Gem::Version
269
+ version: '0'
254
270
  description: A reliable Chef API client with a clean syntax
255
271
  email:
256
272
  - jamie@vialstudios.com
@@ -285,6 +301,7 @@ files:
285
301
  - lib/ridley/middleware/chef_auth.rb
286
302
  - lib/ridley/middleware/chef_response.rb
287
303
  - lib/ridley/middleware/parse_json.rb
304
+ - lib/ridley/middleware/retry.rb
288
305
  - lib/ridley/mixin.rb
289
306
  - lib/ridley/mixin/checksum.rb
290
307
  - lib/ridley/mixin/shell_out.rb
@@ -340,6 +357,7 @@ files:
340
357
  - spec/unit/ridley/bootstrapper/context_spec.rb
341
358
  - spec/unit/ridley/bootstrapper_spec.rb
342
359
  - spec/unit/ridley/chef/cookbook_spec.rb
360
+ - spec/unit/ridley/chef/digester_spec.rb
343
361
  - spec/unit/ridley/client_spec.rb
344
362
  - spec/unit/ridley/connection_spec.rb
345
363
  - spec/unit/ridley/errors_spec.rb
@@ -382,7 +400,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
382
400
  version: '0'
383
401
  segments:
384
402
  - 0
385
- hash: 109610229622215450
403
+ hash: -1703790355388058426
386
404
  requirements: []
387
405
  rubyforge_project:
388
406
  rubygems_version: 1.8.24
@@ -423,6 +441,7 @@ test_files:
423
441
  - spec/unit/ridley/bootstrapper/context_spec.rb
424
442
  - spec/unit/ridley/bootstrapper_spec.rb
425
443
  - spec/unit/ridley/chef/cookbook_spec.rb
444
+ - spec/unit/ridley/chef/digester_spec.rb
426
445
  - spec/unit/ridley/client_spec.rb
427
446
  - spec/unit/ridley/connection_spec.rb
428
447
  - spec/unit/ridley/errors_spec.rb