Skip to content

Commit

Permalink
Merge pull request #319 from puppetlabs/cat-1869-add_configurable_tim…
Browse files Browse the repository at this point in the history
…eout

(CAT-1869) - Add configurable dsc_timeout
  • Loading branch information
gavindidrichsen authored Jun 11, 2024
2 parents b78625a + f7094c8 commit fa44143
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 9 deletions.
21 changes: 19 additions & 2 deletions lib/puppet/provider/dsc_base_provider/dsc_base_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def initialize
@cached_query_results = []
@cached_test_results = []
@logon_failures = []
@timeout = nil # default timeout, ps_manager.execute is expecting nil by default..
super
end

Expand Down Expand Up @@ -251,13 +252,17 @@ def invoke_dsc_resource(context, name_hash, props, method)
context.err('Logon credentials are invalid')
return nil
end
specify_dsc_timeout(name_hash)
resource = invocable_resource(props, context, method)
script_content = ps_script_content(resource)
context.debug("Invoke-DSC Timeout: #{@timeout} milliseconds") if @timeout
context.debug("Script:\n #{redact_secrets(script_content)}")
output = ps_manager.execute(remove_secret_identifiers(script_content))[:stdout]
output = ps_manager.execute(remove_secret_identifiers(script_content), @timeout)[:stdout]

if output.nil?
context.err('Nothing returned')
message = 'Nothing returned.'
message += " There is a timeout of #{@timeout} milliseconds set, ensure the DSC resource has enough time to apply." unless @timeout.nil?
context.err(message)
return nil
end

Expand Down Expand Up @@ -295,6 +300,18 @@ def invoke_dsc_resource(context, name_hash, props, method)
data
end

# Sets the @timeout instance variable.
# @param name_hash [Hash] the hash of namevars to be passed as properties to `Invoke-DscResource`
# The @timeout variable is set to the value of name_hash[:dsc_timeout] in milliseconds
# If name_hash[:dsc_timeout] is nil, @timeout is not changed.
# If @timeout is already set to a value other than nil,
# it is changed only if it's different from name_hash[:dsc_timeout]..
def specify_dsc_timeout(name_hash)
return unless name_hash[:dsc_timeout] && (@timeout.nil? || @timeout != name_hash[:dsc_timeout])

@timeout = name_hash[:dsc_timeout] * 1000
end

# Retries Invoke-DscResource when returned error matches error regex supplied as param.
# @param context [Object] the Puppet runtime context to operate in and send feedback to
# @param max_retry_count [Int] max number of times to retry Invoke-DscResource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@

context 'when the invocation script returns data without errors' do
before do
allow(ps_manager).to receive(:execute).with(script).and_return({ stdout: 'DSC Data' })
allow(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: 'DSC Data' })
allow(JSON).to receive(:parse).with('DSC Data').and_return(parsed_invocation_data)
allow(Puppet::Pops::Time::Timestamp).to receive(:parse).with('2100-01-01').and_return('TimeStamp:2100-01-01')
allow(provider).to receive(:fetch_cached_hashes).and_return([])
Expand Down Expand Up @@ -659,15 +659,15 @@
context 'when the DSC invocation errors' do
it 'writes an error and returns nil' do
expect(provider).not_to receive(:logon_failed_already?)
expect(ps_manager).to receive(:execute).with(script).and_return({ stdout: nil })
expect(context).to receive(:err).with('Nothing returned')
expect(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: nil })
expect(context).to receive(:err).with('Nothing returned.')
expect(result).to be_nil
end
end

context 'when handling DateTimes' do
before do
allow(ps_manager).to receive(:execute).with(script).and_return({ stdout: 'DSC Data' })
allow(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: 'DSC Data' })
allow(JSON).to receive(:parse).with('DSC Data').and_return(parsed_invocation_data)
allow(provider).to receive(:fetch_cached_hashes).and_return([])
end
Expand Down Expand Up @@ -719,7 +719,7 @@
context 'when the credential is invalid' do
before do
allow(provider).to receive(:logon_failed_already?).and_return(false)
allow(ps_manager).to receive(:execute).with(script).and_return({ stdout: 'DSC Data' })
allow(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: 'DSC Data' })
allow(JSON).to receive(:parse).with('DSC Data').and_return({ 'errormessage' => dsc_logon_failure_error })
allow(context).to receive(:err).with(name_hash[:name], puppet_logon_failure_error)
end
Expand Down Expand Up @@ -783,7 +783,7 @@
context 'when the invocation script returns nil' do
it 'errors via context but does not raise' do
expect(ps_manager).to receive(:execute).and_return({ stdout: nil })
expect(context).to receive(:err).with('Nothing returned')
expect(context).to receive(:err).with('Nothing returned.')
expect { result }.not_to raise_error
end
end
Expand Down Expand Up @@ -835,9 +835,29 @@
end
end

context 'when a dsc_timeout is specified' do
let(:should_hash) { name.merge(dsc_timeout: 5) }
let(:apply_props_with_timeout) { { dsc_name: 'foo', dsc_timeout: 5 } }
let(:resource_with_timeout) { "Resource: #{apply_props_with_timeout}" }
let(:script_with_timeout) { "Script: #{apply_props_with_timeout}" }

before do
allow(provider).to receive(:invocable_resource).with(apply_props_with_timeout, context, 'set').and_return(resource_with_timeout)
allow(provider).to receive(:ps_script_content).with(resource_with_timeout).and_return(script_with_timeout)
allow(provider).to receive(:remove_secret_identifiers).with(script_with_timeout).and_return(script_with_timeout)
end

it 'sets @timeout and passes it to ps_manager.execute' do
provider.instance_variable_set(:@timeout, nil)
expect(ps_manager).to receive(:execute).with(script_with_timeout, 5000).and_return({ stdout: '{"in_desired_state": true, "errormessage": null}' })
provider.invoke_set_method(context, name, should_hash)
expect(provider.instance_variable_get(:@timeout)).to eq(5000)
end
end

context 'when the invocation script returns data without errors' do
it 'filters for the correct properties to invoke and returns the results' do
expect(ps_manager).to receive(:execute).with("Script: #{apply_props}").and_return({ stdout: '{"in_desired_state": true, "errormessage": null}' })
expect(ps_manager).to receive(:execute).with("Script: #{apply_props}", nil).and_return({ stdout: '{"in_desired_state": true, "errormessage": null}' })
expect(context).not_to receive(:err)
expect(result).to eq({ 'in_desired_state' => true, 'errormessage' => nil })
end
Expand Down

0 comments on commit fa44143

Please sign in to comment.