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 1, 2021
1 parent 766a9f7 commit 73d5757
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 8 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/auto_release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: "Auto release"

on:
schedule:
- cron: '0 3 * * 6'
workflow_dispatch:

env:
Expand Down
1 change: 1 addition & 0 deletions .pdkignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
/inventory.yaml
/spec/fixtures/litmus_inventory.yaml
/appveyor.yml
/.editorconfig
/.fixtures.yml
/Gemfile
/.gitattributes
Expand Down
5 changes: 5 additions & 0 deletions .sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ Gemfile:
git: 'https://github.com/michaeltlombardi/puppet-resource_api'
branch: 'gh-225/main/custom-insync'
- gem: 'github_changelog_generator'
- gem: 'ruby-pwsh'
- gem: 'webmock'
- gem: 'pry-byebug'
- gem: 'retriable'
version: '~> 3.1'
spec/spec_helper.rb:
mock_with: ':rspec'
.gitlab-ci.yml:
Expand Down
9 changes: 5 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ 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
gem "puppet-resource_api", require: false, git: 'https://github.com/michaeltlombardi/puppet-resource_api', branch: 'gh-225/main/custom-insync'
gem "github_changelog_generator", require: false
gem 'retriable', '~> 3.1', require: false
gem 'pry-byebug', require: false
gem 'webmock', require: false
gem "ruby-pwsh", require: false
gem "webmock", require: false
gem "pry-byebug", require: false
gem "retriable", '~> 3.1', require: false
end
group :system_tests do
gem "puppet-module-posix-system-r#{minor_version}", '~> 1.0', require: false, platforms: [:ruby]
Expand Down
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]}.to_s'"
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,
},
},
)
4 changes: 2 additions & 2 deletions metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"version_requirement": ">= 6.21.0 < 8.0.0"
}
],
"pdk-version": "2.1.0 (1)",
"pdk-version": "2.1.0",
"template-url": "https://github.com/puppetlabs/pdk-templates#main",
"template-ref": "heads/main-0-g0a06ce2"
"template-ref": "tags/2.1.1-0-g03daa92"
}
12 changes: 12 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@
c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT']
c.after(:suite) do
end

# Filter backtrace noise
backtrace_exclusion_patterns = [
%r{spec_helper},
%r{gems},
]

if c.respond_to?(:backtrace_exclusion_patterns)
c.backtrace_exclusion_patterns = backtrace_exclusion_patterns
elsif c.respond_to?(:backtrace_clean_patterns)
c.backtrace_clean_patterns = backtrace_exclusion_patterns
end
end

# Ensures that a module is defined
Expand Down
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 73d5757

Please sign in to comment.