ridley 0.7.0 → 0.8.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 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