Skip to content

Commit

Permalink
(puppetlabs#3) check_powershell resource
Browse files Browse the repository at this point in the history
  • Loading branch information
sheenaajay committed Jul 20, 2021
1 parent 81bff37 commit 69580f0
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 1 deletion.
45 changes: 45 additions & 0 deletions lib/puppet/provider/check_powershell/check_powershell.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require 'puppet/resource_api'
require 'puppet/resource_api/simple_provider'
require 'ruby-pwsh'
require 'retriable'

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

def set(context, changes); end

# Update the check_powershell 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")

posh = Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args)
# 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 = posh.execute(should_hash[:command])
unless should_hash[:expected_exitcode].include? response[:exitcode].to_i
raise Puppet::Error, "check_powershell exitcode check failed. The return exitcode '#{response[:exitcode]}' is not matching with the expected_exitcode '#{should_hash[:expected_exitcode]}'"
end
context.debug("The return exitcode '#{response[:exitcode]}' is matching with the expected_exitcode '#{should_hash[:expected_exitcode]}'")
unless response[:stdout].match(should_hash[:output_matcher])
raise Puppet::Error, "check_powershell output check failed. The return output '#{response[:stdout]}' is not matching output_matcher '#{should_hash[:output_matcher]}'"
end
context.debug("The return output '#{response[:stdout]}' is matching with output_matcher '#{should_hash[:output_matcher]}'")
context.debug("Successfully executed the command '#{should_hash[:command]}'")
return true
end
false
end
end
70 changes: 70 additions & 0 deletions lib/puppet/type/check_powershell.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require 'puppet/resource_api'

Puppet::ResourceApi.register_type(
name: 'check_powershell',
docs: <<-EOS,
@summary a check_powershell type
@example
check_powershell { 'https://www.example.com': }
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: ['custom_insync'],
attributes: {
command: {
type: 'String',
desc: 'The powershell command to run.',
behaviour: :namevar,
},
expected_exitcode: {
type: 'Array[Integer]',
desc: 'An array of acceptable exit codes.',
behaviour: :parameter,
default: [0],
},
output_matcher: {
type: 'Regexp',
desc: 'A call is considered a success if its output matches this regular expression',
behaviour: :parameter,
default: //,
},
execution_timeout: {
type: 'Numeric',
desc: 'Number of seconds for a single execution to wait for a response to return a success before aborting.',
behaviour: :parameter,
default: 60,
},
retries: {
type: 'Integer',
desc: 'Number of requests to make before giving up.',
behaviour: :parameter,
default: 1,
},
backoff: {
type: 'Numeric',
desc: 'Initial number of seconds to wait between requests.',
behaviour: :parameter,
default: 10,
},
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: 120,
},
timeout: {
type: 'Numeric',
desc: 'Number of seconds allocated overall for the check to return a success before giving up.',
behaviour: :parameter,
default: 600,
},
},
)
2 changes: 1 addition & 1 deletion metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,5 @@
],
"pdk-version": "2.1.0",
"template-url": "https://github.com/puppetlabs/pdk-templates#main",
"template-ref": "heads/main-0-g3899cf8"
"template-ref": "tags/2.1.1-0-g03daa92"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

require 'spec_helper'

ensure_module_defined('Puppet::Provider::CheckPowershell')
require 'puppet/provider/check_powershell/check_powershell'

RSpec.describe Puppet::Provider::CheckPowershell::CheckPowershell do
subject(:provider) { described_class.new }

let(:context) { instance_double('Puppet::ResourceApi::BaseContext') }
let(:posh) { instance_double('Pwsh::Manager') }
let(:valid_command) { '$PSVersionTable.PSVersion' }
let(:invalid_command) { 'invalid$PSVersion' }
let(:valid_hash) do
{ name: 'foo', command: valid_command, expected_exitcode: [0], output_matcher: %r{Major}, request_timeout: 30, retries: 1, backoff: 1, exponential_backoff_base: 2, max_backoff: 40, timeout: 60 }
end
let(:invalid_hash) do
{ name: 'foos', command: invalid_command, expected_exitcode: [2], output_matcher: %r{test}, request_timeout: 30, retries: 3, backoff: 1, exponential_backoff_base: 2, max_backoff: 40, timeout: 60 }
end

describe 'get(context)' do
it 'processes resources' do
expect(provider.get(context)).to eq []
end
end

describe 'insync?(context, name, attribute_name, is_hash, should_hash) without Retry' do
it 'processes resources' do
allow(Pwsh::Manager).to receive(:powershell_path).and_return('C:\\Windows')
allow(Pwsh::Manager).to receive(:powershell_args).and_return(['-NoProfile'])
allow(Pwsh::Manager).to receive(:instance).with(any_args).and_return(posh)
allow(posh).to receive(:execute).with(valid_command).and_return({ stdout: 'Major', exitcode: 0 })
expect(context).to receive(:debug).with('Checking whether foo is up-to-date')
expect(context).to receive(:debug).with("The return exitcode '0' is matching with the expected_exitcode '[0]'")
expect(context).to receive(:debug).with("The return output 'Major' is matching with output_matcher '(?-mix:Major)'")
expect(context).to receive(:debug).with("Successfully executed the command '$PSVersionTable.PSVersion'")
expect(provider.insync?(context, 'foo', 'foo', valid_hash, valid_hash)).to be(true)
end
end

describe 'insync?(context, name, attribute_name, is_hash, should_hash) expected_exitcode not matching' do
it 'processes resources' do
allow(Pwsh::Manager).to receive(:powershell_path).and_return('C:\\Windows')
allow(Pwsh::Manager).to receive(:powershell_args).and_return(['-NoProfile'])
allow(Pwsh::Manager).to receive(:instance).with(any_args).and_return(posh)
allow(posh).to receive(:execute).with(invalid_command).and_return({ stdout: 'Major', exitcode: 3 })
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(:info).with(%r{1 tries})
expect(context).to receive(:info).with(%r{2 tries})
expect(context).to receive(:info).with(%r{3 tries})
expect { provider.insync?(context, 'foo', 'foo', invalid_hash, invalid_hash) }.to raise_error(%r{check_powershell exitcode check failed.})
end
end

describe 'insync?(context, name, attribute_name, is_hash, should_hash) output_matcher not matching' do
it 'processes resources' do
allow(Pwsh::Manager).to receive(:powershell_path).and_return('C:\\Windows')
allow(Pwsh::Manager).to receive(:powershell_args).and_return(['-NoProfile'])
allow(Pwsh::Manager).to receive(:instance).with(any_args).and_return(posh)
allow(posh).to receive(:execute).with(invalid_command).and_return({ stdout: 'invalid', exitcode: 2 })
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 exitcode '2' is matching with the expected_exitcode '[2]'")
expect(context).to receive(:info).with(%r{1 tries})
expect(context).to receive(:info).with(%r{2 tries})
expect(context).to receive(:info).with(%r{3 tries})
expect { provider.insync?(context, 'foo', 'foo', invalid_hash, invalid_hash) }.to raise_error(%r{check_powershell output check failed.})
end
end

describe 'insync?(context, name, attribute_name, is_hash, should_hash) with Retry' do
it 'processes resources' do
allow(Pwsh::Manager).to receive(:powershell_path).and_return('C:\\Windows')
allow(Pwsh::Manager).to receive(:powershell_args).and_return(['-NoProfile'])
allow(Pwsh::Manager).to receive(:instance).with(any_args).and_return(posh)
allow(context).to receive(:debug)
allow(context).to receive(:debug)
expect(context).to receive(:debug).with('Checking whether foo is up-to-date')
allow(posh).to receive(:execute).with(invalid_command).and_raise(StandardError)
expect(context).to receive(:info).with(%r{1 tries})
expect(context).to receive(:info).with(%r{2 tries})
expect(context).to receive(:info).with(%r{3 tries})
expect { provider.insync?(context, 'foo', 'foo', invalid_hash, invalid_hash) }.to raise_error(StandardError)
end
end
end
10 changes: 10 additions & 0 deletions spec/unit/puppet/type/check_powershell_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require 'spec_helper'
require 'puppet/type/check_powershell'

RSpec.describe 'the check_powershell type' do
it 'loads' do
expect(Puppet::Type.type(:check_powershell)).not_to be_nil
end
end

0 comments on commit 69580f0

Please sign in to comment.