Skip to content

Commit

Permalink
Merge pull request puppetlabs#13 from sheenaajay/opv/issue1
Browse files Browse the repository at this point in the history
(puppetlabs#1) update check_http provider and type with new attributes
  • Loading branch information
DavidS authored Jun 30, 2021
2 parents a7f60f2 + 220c8d8 commit 766a9f7
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 40 deletions.
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ group :development do
gem "puppet-module-posix-dev-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby]
gem "puppet-module-win-default-r#{minor_version}", '~> 1.0', require: false, platforms: [:mswin, :mingw, :x64_mingw]
gem "puppet-module-win-dev-r#{minor_version}", '~> 1.0', require: false, platforms: [:mswin, :mingw, :x64_mingw]
gem "puppet-resource_api", require: false, git: 'https://github.com/michaeltlombardi/puppet-resource_api', branch: 'gh-225/main/custom-insync'
gem "puppet-resource_api", require: false
gem "github_changelog_generator", require: false
gem 'retriable', '~> 3.1', require: false
gem 'pry-byebug', require: false
gem 'webmock', require: false
end
group :system_tests do
gem "puppet-module-posix-system-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby]
Expand Down
39 changes: 32 additions & 7 deletions lib/puppet/provider/check_http/check_http.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
# frozen_string_literal: true

require 'puppet/resource_api'
require 'puppet/resource_api/simple_provider'
require 'net/http'
require 'retriable'

# Implementation for the check_http type using the Resource API.
class Puppet::Provider::CheckHttp::CheckHttp
def get(_context)
[]
end

def set(context, changes)
changes.each do |name, _change|
uri = URI(name)
context.processing(uri.to_s, {}, {}, message: 'checking http') do
if Net::HTTP.get(uri)
context.info("successfully connected to #{name}")
end
def set(context, changes); end

# Update the check_http provider to use the above attributes to execute up to retries number of times
# with success being defined as having one of the expected_statuses
# and the body of the response matches body_matcher while taking into account request_timeout.

def insync?(context, name, attribute_name, _is_hash, should_hash)
context.debug("Checking whether #{attribute_name} is up-to-date")
uri = URI.parse(should_hash[:url])

# This callback provides the exception that was raised in the current try, the try_number, the elapsed_time for all tries so far, and the time in seconds of the next_interval.
do_this_on_each_retry = proc do |exception, try, elapsed_time, next_interval|
context.info("#{exception.class}: '#{exception.message}' - #{try} tries in #{elapsed_time} seconds and #{next_interval} seconds until the next try.") unless exception.nil?
end

Retriable.retriable(tries: should_hash[:retries], max_elapsed_time: should_hash[:request_timeout], max_interval: should_hash[:max_backoff],
multiplier: should_hash[:exponential_backoff_base], on_retry: do_this_on_each_retry) do
response = Net::HTTP.get_response(uri)

unless should_hash[:expected_statuses].include? response.code.to_i
raise Puppet::Error, "check_http response code check failed. The return response '#{response.code}' is not matching with the expected_statuses '#{should_hash[:expected_statuses]}.to_s'"
end
context.debug("The return response '#{response.code}' is matching with the expected_statuses '#{should_hash[:expected_statuses]}'")
unless response.body.match(should_hash[:body_matcher])
raise Puppet::Error, "check_http response body check failed. The return response body '#{response.body[0..99]}' is not matching body_matcher '#{should_hash[:body_matcher].to_s}'"
end
context.debug("The return response body '#{response.body[0..99]}' is matching with body_matcher '#{should_hash[:body_matcher].to_s}'")
context.debug("Successfully connected to '#{name}'")
return true
end
false
end
end
61 changes: 55 additions & 6 deletions lib/puppet/type/check_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,66 @@
Use this to check whether a web server is responding correctly. This can be used both as a prerequisite (don't manage something if a dependency is unhealthy) or to check whether everything went right after managing something.
EOS
features: [],
features: ['custom_insync'],
attributes: {
ensure: {
type: 'Enum[present, absent]',
desc: 'Set to `absent` to temporarily disable a check.',
default: 'present',
},
url: {
type: 'String',
desc: 'The URL to test.',
behaviour: :namevar,
},
headers: {
type: 'Hash[String, String]',
desc: 'A hash of headers to pass along with the request.',
behaviour: :parameter,
default: {},
},
expected_statuses: {
type: 'Array[Integer]',
desc: 'An array of acceptable HTTP status codes. If a request returns one of these status codes, it is considered a success',
behaviour: :parameter,
default: [200],
},
body_matcher: {
type: 'Regexp',
desc: 'A request is considered a success if the body of the HTTP response matches this regular expression',
behaviour: :parameter,
default: //,
},
request_timeout: {
type: 'Numeric',
desc: 'Number of seconds for a single request to wait for a response to return a success before aborting.',
behaviour: :parameter,
default: 10,
},
retries: {
type: 'Integer',
desc: 'Number of requests to make before giving up.',
behaviour: :parameter,
default: 3,
},
backoff: {
type: 'Numeric',
desc: 'Initial number of seconds to wait between requests.',
behaviour: :parameter,
default: 1,
},
exponential_backoff_base: {
type: 'Numeric',
desc: 'Exponential base for the exponential backoff calculations.',
behaviour: :parameter,
default: 2,
},
max_backoff: {
type: 'Numeric',
desc: 'An upper limit to the backoff duration,.',
behaviour: :parameter,
default: 10,
},
timeout: {
type: 'Numeric',
desc: 'Number of seconds allocated overall for the check to return a success before giving up.',
behaviour: :parameter,
default: 60,
},
},
)
10 changes: 10 additions & 0 deletions spec/acceptance/check_http.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
check_http {'https://www.google.com':
expected_statuses => [200],
body_matcher => /Google/,
request_timeout => 30,
retries => 3,
backoff => 1,
exponential_backoff_base => 2,
max_backoff => 40,
timeout => 60,
}
98 changes: 72 additions & 26 deletions spec/unit/puppet/provider/check_http/check_http_spec.rb
Original file line number Diff line number Diff line change
@@ -1,52 +1,98 @@
# frozen_string_literal: true

require 'spec_helper'
require 'webmock/rspec'

ensure_module_defined('Puppet::Provider::CheckHttp')
require 'puppet/provider/check_http/check_http'

RSpec.describe Puppet::Provider::CheckHttp::CheckHttp do
subject(:provider) { described_class.new }
WebMock.disable_net_connect!(allow_localhost: true)

let(:context) { instance_double('Puppet::ResourceApi::BaseContext', 'context') }
let(:context) { double('Puppet::ResourceApi::BaseContext') }
let(:valid_uri) { 'https://www.google.com' }
let(:invalid_uri) { 'https://abc.test.net' }
let(:valid_hash) { { name: 'foo', url: valid_uri, ensure: 'present',expected_statuses: [200], body_matcher: /Google/, request_timeout: 30, retries: 3, backoff: 1, exponential_backoff_base:2, max_backoff:40, timeout:60 } }
let(:invalid_hash) { { name: 'foos', url: invalid_uri, ensure: 'present',expected_statuses: [200], body_matcher: /Google/, request_timeout: 30, retries: 3, backoff: 1, exponential_backoff_base:2, max_backoff:40, timeout:60 } }

describe '#get' do
describe 'get(context)' do
it 'processes resources' do
expect(context).to receive(:debug).with('Returning pre-canned example data')
expect(provider.get(context)).to eq [
{
name: 'foo',
ensure: 'present',
},
{
name: 'bar',
ensure: 'present',
},
]
expect(provider.get(context)).to eq []
end
end

describe 'create(context, name, should)' do
it 'creates the resource' do
expect(context).to receive(:notice).with(%r{\ACreating 'a'})

provider.create(context, 'a', name: 'a', ensure: 'present')
describe 'insync?(context, name, attribute_name, is_hash, should_hash) without Retry' do
it 'processes resources' do
stub_request(:get, "https://www.google.com/").
with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'www.google.com',
'User-Agent'=>'Ruby'
}).to_return(status: 200, body: "Google", headers: {})
expect(context).to receive(:debug).with('Checking whether foo is up-to-date')
expect(context).to receive(:debug).with("The return response '200' is matching with the expected_statuses '[200]'")
expect(context).to receive(:debug).with("The return response body 'Google' is matching with body_matcher '(?-mix:Google)'")
expect(context).to receive(:debug).with("Successfully connected to 'foo'")
expect(provider.insync?(context, 'foo', 'foo', valid_hash, valid_hash)).to be(true)
end
end

describe 'update(context, name, should)' do
it 'updates the resource' do
expect(context).to receive(:notice).with(%r{\AUpdating 'foo'})
describe 'insync?(context, name, attribute_name, is_hash, should_hash) expected_status not matching' do
it 'processes resources' do
stub_request(:get, invalid_uri).
with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'abc.test.net',
'User-Agent'=>'Ruby'
}).to_return(status: 500, body: "invalidbody", headers: {})
allow(context).to receive(:debug)
allow(context).to receive(:debug)
expect(context).to receive(:debug).with('Checking whether foo is up-to-date')
expect { provider.insync?(context, 'foo', 'foo', invalid_hash, invalid_hash) }.to raise_error(/check_http response code check failed./)
end
end

provider.update(context, 'foo', name: 'foo', ensure: 'present')
describe 'insync?(context, name, attribute_name, is_hash, should_hash) body_matcher not matching' do
it 'processes resources' do
stub_request(:get, invalid_uri).
with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'abc.test.net',
'User-Agent'=>'Ruby'
}).to_return(status: 200, body: "invalidbody", headers: {})
allow(context).to receive(:debug)
allow(context).to receive(:debug)
expect(context).to receive(:debug).with('Checking whether foo is up-to-date')
expect(context).to receive(:debug).with("The return response '200' is matching with the expected_statuses '[200]'")
expect { provider.insync?(context, 'foo', 'foo', invalid_hash, invalid_hash) }.to raise_error(/check_http response body check failed./)
end
end

describe 'delete(context, name)' do
it 'deletes the resource' do
expect(context).to receive(:notice).with(%r{\ADeleting 'foo'})
describe 'insync?(context, name, attribute_name, is_hash, should_hash) with Retry' do
it 'processes resources' do
allow(context).to receive(:debug)
allow(context).to receive(:debug)
expect(context).to receive(:debug).with('Checking whether foo is up-to-date')
stub_request(:get, invalid_uri).
with(
headers: {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'abc.test.net',
'User-Agent'=>'Ruby'
}).to_raise(StandardError)
expect(context).to receive(:info).with(/StandardError: 'Exception from WebMock' - 1 tries/)
expect(context).to receive(:info).with(/StandardError: 'Exception from WebMock' - 2 tries/)
expect(context).to receive(:info).with(/StandardError: 'Exception from WebMock' - 3 tries/)

provider.delete(context, 'foo')
expect { provider.insync?(context, 'foo', 'foo', invalid_hash, invalid_hash) }.to raise_error(StandardError)
end
end
end

0 comments on commit 766a9f7

Please sign in to comment.