ruby-pwsh 1.0.1 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd8ad4546d9fbdc7bf62c17f8132adb9647e311e9e3319b22b5161f924fd94c3
4
- data.tar.gz: c607771ae691c4593b839cc1134d3e0fcc15b0a454eef93a3b146301d13b3411
3
+ metadata.gz: 1ac282abeea132104fda1b3fc2f4743f4b99603cdd6b73a42b7063641c9e65d9
4
+ data.tar.gz: 410bc1d7a7b08366a89019324b172219102197f8aa6891bcfaa1729e3bd76925
5
5
  SHA512:
6
- metadata.gz: e8efb9f23a1fe2302369938416d08e463c4c5e088712dac293b10f0e8abc09431f91057039f95f918382e4775b932da4ab270901591f58d834a01c1d784d4fe6
7
- data.tar.gz: 788c3c601d46c37478b41abe5a068eac7da7c0912ec71f703841f4d462a3363d2c613bbeb8356b6a38b21a3870eec0d567e4bfa2d2892a40aadd1e4a62dc38e1
6
+ metadata.gz: 0fd8c7af3f87ce61121a7404b66cfae5cf78fea8bdb885dc7715467bb5092bd974a9fd4d7f94824fe10675d56b4e4dc7b439826f95bc5d9672f14fcb74692501
7
+ data.tar.gz: 198328e45920aa2794cab2984e090c14c81ce56b4b038e4ae0b7f22ad66d327d0bbd34b5726709108c14609324dba1687a83a2f67026a2d8abf12d749067ca27
data/.rubocop.yml CHANGED
@@ -18,6 +18,9 @@ AllCops:
18
18
  Style/ClassAndModuleChildren:
19
19
  Enabled: false
20
20
 
21
+ Layout/LineLength:
22
+ Max: 196
23
+
21
24
  ####################################################
22
25
  # Cops below here due for deprecation
23
26
  ####################################################
@@ -5,7 +5,7 @@ require 'ruby-pwsh'
5
5
  require 'pathname'
6
6
  require 'json'
7
7
 
8
- class Puppet::Provider::DscBaseProvider
8
+ class Puppet::Provider::DscBaseProvider # rubocop:disable Metrics/ClassLength
9
9
  # Initializes the provider, preparing the instance variables which cache:
10
10
  # - the canonicalized resources across calls
11
11
  # - query results
@@ -44,6 +44,7 @@ class Puppet::Provider::DscBaseProvider
44
44
  # @param context [Object] the Puppet runtime context to operate in and send feedback to
45
45
  # @param resources [Hash] the hash of the resource to canonicalize from either manifest or invocation
46
46
  # @return [Hash] returns a hash representing the current state of the object, if it exists
47
+ # rubocop:disable Metrics/BlockLength, Metrics/MethodLength
47
48
  def canonicalize(context, resources)
48
49
  canonicalized_resources = []
49
50
  resources.collect do |r|
@@ -83,9 +84,18 @@ class Puppet::Provider::DscBaseProvider
83
84
  downcased_result.each do |key, value|
84
85
  # Canonicalize to the manifest value unless the downcased strings match and the attribute is not an enum:
85
86
  # - When the values don't match at all, the manifest value is desired;
86
- # - When the values match case insensitively but the attribute is an enum, prefer the casing of the manifest enum.
87
- # - When the values match case insensitively and the attribute is not an enum, prefer the casing from invoke_get_method
88
- canonicalized[key] = r[key] unless same?(value, downcased_resource[key]) && !enum_attributes(context).include?(key)
87
+ # - When the values match case insensitively but the attribute is an enum, and the casing from invoke_get_method
88
+ # is not int the enum, prefer the casing of the manifest enum.
89
+ # - When the values match case insensitively and the attribute is not an enum, or is an enum and invoke_get_method casing
90
+ # is in the enum, prefer the casing from invoke_get_method
91
+ is_enum = enum_attributes(context).include?(key)
92
+ canonicalized_value_in_enum = if is_enum
93
+ enum_values(context, key).include?(canonicalized[key])
94
+ else
95
+ false
96
+ end
97
+ match_insensitively = same?(value, downcased_resource[key])
98
+ canonicalized[key] = r[key] unless match_insensitively && (canonicalized_value_in_enum || !is_enum)
89
99
  canonicalized.delete(key) unless downcased_resource.key?(key)
90
100
  end
91
101
  # Cache the actually canonicalized resource separately
@@ -104,6 +114,7 @@ class Puppet::Provider::DscBaseProvider
104
114
  context.debug("Canonicalized Resources: #{canonicalized_resources}")
105
115
  canonicalized_resources
106
116
  end
117
+ # rubocop:enable Metrics/BlockLength, Metrics/MethodLength
107
118
 
108
119
  # Attempts to retrieve an instance of the DSC resource, invoking the `Get` method and passing any
109
120
  # namevars as the Properties to Invoke-DscResource. The result object, if any, is compared to the
@@ -244,6 +255,7 @@ class Puppet::Provider::DscBaseProvider
244
255
  script_content = ps_script_content(resource)
245
256
  context.debug("Script:\n #{redact_secrets(script_content)}")
246
257
  output = ps_manager.execute(remove_secret_identifiers(script_content))[:stdout]
258
+
247
259
  if output.nil?
248
260
  context.err('Nothing returned')
249
261
  return nil
@@ -256,8 +268,10 @@ class Puppet::Provider::DscBaseProvider
256
268
  return nil
257
269
  end
258
270
  context.debug("raw data received: #{data.inspect}")
271
+ collision_error_matcher = /The Invoke-DscResource cmdlet is in progress and must return before Invoke-DscResource can be invoked/
259
272
 
260
273
  error = data['errormessage']
274
+
261
275
  unless error.nil? || error.empty?
262
276
  # NB: We should have a way to stop processing this resource *now* without blowing up the whole Puppet run
263
277
  # Raising an error stops processing but blows things up while context.err alerts but continues to process
@@ -267,6 +281,11 @@ class Puppet::Provider::DscBaseProvider
267
281
  @logon_failures << name_hash[:dsc_psdscrunascredential].dup
268
282
  # This is a hack to handle the query cache to prevent a second lookup
269
283
  @cached_query_results << name_hash # if fetch_cached_hashes(@cached_query_results, [data]).empty?
284
+ elsif error.match?(collision_error_matcher)
285
+ context.notice('Invoke-DscResource collision detected: Please stagger the timing of your Puppet runs as this can lead to unexpected behaviour.')
286
+ retry_invoke_dsc_resource(context, 5, 60, collision_error_matcher) do
287
+ data = ps_manager.execute(remove_secret_identifiers(script_content))[:stdout]
288
+ end
270
289
  else
271
290
  context.err(error)
272
291
  end
@@ -276,6 +295,35 @@ class Puppet::Provider::DscBaseProvider
276
295
  data
277
296
  end
278
297
 
298
+ # Retries Invoke-DscResource when returned error matches error regex supplied as param.
299
+ # @param context [Object] the Puppet runtime context to operate in and send feedback to
300
+ # @param max_retry_count [Int] max number of times to retry Invoke-DscResource
301
+ # @param retry_wait_interval_secs [Int] Time delay between retries
302
+ # @param error_matcher [String] the regex pattern to match with error
303
+ def retry_invoke_dsc_resource(context, max_retry_count, retry_wait_interval_secs, error_matcher)
304
+ try = 0
305
+ while try < max_retry_count
306
+ try += 1
307
+ # notify and wait for retry interval
308
+ context.notice("Sleeping for #{retry_wait_interval_secs} seconds.")
309
+ sleep retry_wait_interval_secs
310
+ # notify and retry
311
+ context.notice("Retrying: attempt #{try} of #{max_retry_count}.")
312
+ data = JSON.parse(yield)
313
+ # if no error, assume successful invocation and break
314
+ break if data['errormessage'].nil?
315
+
316
+ # notify of failed retry
317
+ context.notice("Attempt #{try} of #{max_retry_count} failed.")
318
+ # return if error does not match expceted error, or all retries exhausted
319
+ return context.err(data['errormessage']) unless data['errormessage'].match?(error_matcher) && try < max_retry_count
320
+
321
+ # else, retry
322
+ next
323
+ end
324
+ data
325
+ end
326
+
279
327
  # Determine if the DSC Resource is in the desired state, invoking the `Test` method unless it's
280
328
  # already been run for the resource, in which case reuse the result instead of checking for each
281
329
  # property. This behavior is only triggered if the validation_mode is set to resource; by default
@@ -303,7 +351,7 @@ class Puppet::Provider::DscBaseProvider
303
351
  # @param context [Object] the Puppet runtime context to operate in and send feedback to
304
352
  # @param name_hash [Hash] the hash of namevars to be passed as properties to `Invoke-DscResource`
305
353
  # @return [Hash] returns a hash representing the DSC resource munged to the representation the Puppet Type expects
306
- def invoke_get_method(context, name_hash)
354
+ def invoke_get_method(context, name_hash) # rubocop:disable Metrics/AbcSize
307
355
  context.debug("retrieving #{name_hash.inspect}")
308
356
 
309
357
  query_props = name_hash.select { |k, v| mandatory_get_attributes(context).include?(k) || (k == :dsc_psdscrunascredential && !v.nil?) }
@@ -346,6 +394,8 @@ class Puppet::Provider::DscBaseProvider
346
394
  # If a resource is found, it's present, so refill this Puppet-only key
347
395
  data[:name] = name_hash[:name]
348
396
 
397
+ data = stringify_nil_attributes(context, data)
398
+
349
399
  # Have to check for this to avoid a weird canonicalization warning
350
400
  # The Resource API calls canonicalize against the current state which
351
401
  # will lead to dsc_ensure being set to absent in the name_hash even if
@@ -615,6 +665,20 @@ class Puppet::Provider::DscBaseProvider
615
665
  context.type.attributes.select { |_attribute, properties| properties[:mandatory_for_set] }.keys
616
666
  end
617
667
 
668
+ # Parses the DSC resource type definition to retrieve the names of any attributes which are specifed as required strings
669
+ # This is used to ensure that any nil values are converted to empty strings to match puppets expecetd value
670
+ # @param context [Object] the Puppet runtime context to operate in and send feedback to
671
+ # @param data [Hash] the hash of properties returned from the DSC resource
672
+ # @return [Hash] returns a data hash with any nil values converted to empty strings
673
+ def stringify_nil_attributes(context, data)
674
+ nil_strings = data.select { |_name, value| value.nil? }.keys
675
+ string_attrs = context.type.attributes.select { |_name, properties| properties[:type] == 'String' }.keys
676
+ string_attrs.each do |attribute|
677
+ data[attribute] = '' if nil_strings.include?(attribute)
678
+ end
679
+ data
680
+ end
681
+
618
682
  # Parses the DSC resource type definition to retrieve the names of any attributes which are specified as namevars
619
683
  #
620
684
  # @param context [Object] the Puppet runtime context to operate in and send feedback to
@@ -641,6 +705,28 @@ class Puppet::Provider::DscBaseProvider
641
705
  context.type.attributes.select { |_name, properties| properties[:type].include?('Enum[') }.keys
642
706
  end
643
707
 
708
+ # Parses the DSC resource type definition to retrieve the values of any attributes which are specified as enums
709
+ #
710
+ # @param context [Object] the Puppet runtime context to operate in and send feedback to
711
+ # @param attribute [String] the enum attribute to retrieve the allowed values from
712
+ # @return [Array] returns an array of attribute names as symbols which are enums
713
+ def enum_values(context, attribute)
714
+ # Get the attribute's type string for the given key
715
+ type_string = context.type.attributes[attribute][:type]
716
+
717
+ # Return an empty array if the key doesn't have an Enum type or doesn't exist
718
+ return [] unless type_string&.include?('Enum[')
719
+
720
+ # Extract the enum values from the type string
721
+ enum_content = type_string.match(/Enum\[(.*?)\]/)&.[](1)
722
+
723
+ # Return an empty array if we couldn't find the enum values
724
+ return [] if enum_content.nil?
725
+
726
+ # Return an array of the enum values, stripped of extra whitespace and quote marks
727
+ enum_content.split(',').map { |val| val.strip.delete('\'') }
728
+ end
729
+
644
730
  # Look through a fully formatted string, replacing all instances where a value matches the formatted properties
645
731
  # of an instantiated variable with references to the variable instead. This allows us to pass complex and nested
646
732
  # CIM instances to the Invoke-DscResource parameter hash without constructing them *in* the hash.
@@ -666,7 +752,7 @@ class Puppet::Provider::DscBaseProvider
666
752
  # @param resource [Hash] a hash with the information needed to run `Invoke-DscResource`
667
753
  # @return [String] A multi-line string which sets the PSModulePath at the system level
668
754
  def munge_psmodulepath(resource)
669
- vendor_path = resource[:vendored_modules_path].tr('/', '\\')
755
+ vendor_path = resource[:vendored_modules_path]&.tr('/', '\\')
670
756
  <<~MUNGE_PSMODULEPATH.strip
671
757
  $UnmungedPSModulePath = [System.Environment]::GetEnvironmentVariable('PSModulePath','machine')
672
758
  $MungedPSModulePath = $env:PSModulePath + ';#{vendor_path}'
data/lib/pwsh/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Pwsh
4
4
  # The version of the ruby-pwsh gem
5
- VERSION = '1.0.1'
5
+ VERSION = '1.1.1'
6
6
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if ENV['COVERAGE'] == 'yes'
4
+ begin
5
+ require 'simplecov'
6
+ require 'simplecov-console'
7
+
8
+ SimpleCov.formatters = [
9
+ SimpleCov::Formatter::HTMLFormatter,
10
+ SimpleCov::Formatter::Console
11
+ ]
12
+
13
+ SimpleCov.start do
14
+ track_files 'lib/**/*.rb'
15
+
16
+ add_filter '/spec'
17
+ add_filter 'lib/pwsh/version.rb'
18
+
19
+ # do not track vendored files
20
+ add_filter '/vendor'
21
+ add_filter '/.vendor'
22
+ end
23
+ rescue LoadError
24
+ raise 'Add the simplecov & simplecov-console gems to Gemfile to enable this task'
25
+ end
26
+ end
27
+
3
28
  require 'bundler/setup'
4
29
  require 'ruby-pwsh'
5
30
 
@@ -2,23 +2,24 @@
2
2
 
3
3
  require 'spec_helper'
4
4
  require 'puppet/type'
5
+ require 'puppet/resource_api'
5
6
  require 'puppet/provider/dsc_base_provider/dsc_base_provider'
6
7
  require 'json'
7
8
 
8
9
  RSpec.describe Puppet::Provider::DscBaseProvider do
9
10
  subject(:provider) { described_class.new }
10
11
 
11
- let(:context) { instance_double(Puppet::ResourceApi::PuppetContext) }
12
- let(:type) { instance_double(Puppet::ResourceApi::TypeDefinition) }
12
+ let(:context) { instance_double(Puppet::ResourceApi::BaseContext, 'context') }
13
+ let(:type) { instance_double(Puppet::ResourceApi::TypeDefinition, 'typedef') }
13
14
  let(:ps_manager) { instance_double(Pwsh::Manager) }
14
15
  let(:execute_response) { { stdout: nil, stderr: nil, exitcode: 0 } }
15
16
 
16
17
  # Reset the caches after each run
17
18
  after do
18
- described_class.instance_variable_set(:@cached_canonicalized_resource, [])
19
- described_class.instance_variable_set(:@cached_query_results, [])
20
- described_class.instance_variable_set(:@cached_test_results, [])
21
- described_class.instance_variable_set(:@logon_failures, [])
19
+ provider.instance_variable_set(:@cached_canonicalized_resource, [])
20
+ provider.instance_variable_set(:@cached_query_results, [])
21
+ provider.instance_variable_set(:@cached_test_results, [])
22
+ provider.instance_variable_set(:@logon_failures, [])
22
23
  end
23
24
 
24
25
  describe '.initialize' do
@@ -28,27 +29,30 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
28
29
  end
29
30
 
30
31
  it 'initializes the cached_canonicalized_resource instance variable' do
31
- expect(described_class.instance_variable_get(:@cached_canonicalized_resource)).to eq([])
32
+ expect(provider.instance_variable_get(:@cached_canonicalized_resource)).to eq([])
32
33
  end
33
34
 
34
35
  it 'initializes the cached_query_results instance variable' do
35
- expect(described_class.instance_variable_get(:@cached_query_results)).to eq([])
36
+ expect(provider.instance_variable_get(:@cached_query_results)).to eq([])
36
37
  end
37
38
 
38
39
  it 'initializes the cached_test_results instance variable' do
39
- expect(described_class.instance_variable_get(:@cached_test_results)).to eq([])
40
+ expect(provider.instance_variable_get(:@cached_test_results)).to eq([])
40
41
  end
41
42
 
42
43
  it 'initializes the logon_failures instance variable' do
43
- expect(described_class.instance_variable_get(:@logon_failures)).to eq([])
44
+ expect(provider.instance_variable_get(:@logon_failures)).to eq([])
44
45
  end
45
46
  end
46
47
 
47
48
  describe '.cached_test_results' do
48
49
  let(:cache_value) { %w[foo bar] }
49
50
 
51
+ before do
52
+ provider.instance_variable_set(:@cached_test_results, cache_value)
53
+ end
54
+
50
55
  it 'returns the value of the @cached_test_results instance variable' do
51
- described_class.instance_variable_set(:@cached_test_results, cache_value)
52
56
  expect(provider.cached_test_results).to eq(cache_value)
53
57
  end
54
58
  end
@@ -175,6 +179,8 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
175
179
  end
176
180
 
177
181
  it 'treats the manifest value as canonical' do
182
+ expect(context).to receive(:type).and_return(type)
183
+ expect(type).to receive(:attributes).and_return({ dsc_property: { type: "Enum['Dword']" } })
178
184
  expect(canonicalized_resource.first[:dsc_property]).to eq('Dword')
179
185
  end
180
186
  end
@@ -237,11 +243,11 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
237
243
 
238
244
  describe '.get' do
239
245
  after do
240
- described_class.instance_variable_set(:@cached_canonicalized_resource, [])
246
+ provider.instance_variable_set(:@cached_canonicalized_resource, [])
241
247
  end
242
248
 
243
249
  it 'checks the cached results, returning if one exists for the specified names' do
244
- described_class.instance_variable_set(:@cached_canonicalized_resource, [])
250
+ provider.instance_variable_set(:@cached_canonicalized_resource, [])
245
251
  allow(context).to receive(:debug)
246
252
  expect(provider).to receive(:fetch_cached_hashes).with([], [{ name: 'foo' }]).and_return([{ name: 'foo', property: 'bar' }])
247
253
  expect(provider).not_to receive(:invoke_get_method)
@@ -249,7 +255,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
249
255
  end
250
256
 
251
257
  it 'adds mandatory properties to the name hash when calling invoke_get_method' do
252
- described_class.instance_variable_set(:@cached_canonicalized_resource, [{ name: 'foo', property: 'bar', dsc_some_parameter: 'baz' }])
258
+ provider.instance_variable_set(:@cached_canonicalized_resource, [{ name: 'foo', property: 'bar', dsc_some_parameter: 'baz' }])
253
259
  allow(context).to receive(:debug)
254
260
  expect(provider).to receive(:fetch_cached_hashes).with([], [{ name: 'foo' }]).and_return([])
255
261
  expect(provider).to receive(:namevar_attributes).and_return([:name]).exactly(3).times
@@ -530,7 +536,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
530
536
  end
531
537
 
532
538
  after do
533
- described_class.instance_variable_set(:@cached_query_results, nil)
539
+ provider.instance_variable_set(:@cached_query_results, [])
534
540
  end
535
541
 
536
542
  context 'when the invocation script returns data without errors' do
@@ -557,7 +563,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
557
563
 
558
564
  it 'caches the result' do
559
565
  expect { result }.not_to raise_error
560
- expect(described_class.instance_variable_get(:@cached_query_results)).to eq([result])
566
+ expect(provider.instance_variable_get(:@cached_query_results)).to eq([result])
561
567
  end
562
568
 
563
569
  it 'removes unrelated properties from the result' do
@@ -719,7 +725,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
719
725
  end
720
726
 
721
727
  after do
722
- described_class.instance_variable_set(:@logon_failures, [])
728
+ provider.instance_variable_set(:@logon_failures, [])
723
729
  end
724
730
 
725
731
  it 'errors specifically for a logon failure and returns nil' do
@@ -728,12 +734,12 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
728
734
 
729
735
  it 'caches the logon failure' do
730
736
  expect { result }.not_to raise_error
731
- expect(described_class.instance_variable_get(:@logon_failures)).to eq([credential_hash])
737
+ expect(provider.instance_variable_get(:@logon_failures)).to eq([credential_hash])
732
738
  end
733
739
 
734
740
  it 'caches the query results' do
735
741
  expect { result }.not_to raise_error
736
- expect(described_class.instance_variable_get(:@cached_query_results)).to eq([name_hash])
742
+ expect(provider.instance_variable_get(:@cached_query_results)).to eq([name_hash])
737
743
  end
738
744
  end
739
745
 
@@ -790,6 +796,45 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
790
796
  end
791
797
  end
792
798
 
799
+ context 'when the invocation script errors with a collision' do
800
+ it 'writes a notice via context and applies successfully on retry' do
801
+ expect(ps_manager).to receive(:execute).and_return({ stdout: '{"errormessage": "The Invoke-DscResource cmdlet is in progress and must return before Invoke-DscResource can be invoked"}' })
802
+ expect(context).to receive(:notice).with(/Invoke-DscResource collision detected: Please stagger the timing of your Puppet runs as this can lead to unexpected behaviour./).once
803
+ expect(context).to receive(:notice).with('Sleeping for 60 seconds.').twice
804
+ expect(context).to receive(:notice).with(/Retrying: attempt [1-2] of 5/).twice
805
+ expect(ps_manager).to receive(:execute).and_return({ stdout: '{"errormessage": "The Invoke-DscResource cmdlet is in progress and must return before Invoke-DscResource can be invoked"}' })
806
+ expect(context).to receive(:notice).with('Attempt 1 of 5 failed.')
807
+ allow(provider).to receive(:sleep)
808
+ expect(ps_manager).to receive(:execute).and_return({ stdout: '{"errormessage": null}' })
809
+ expect { result }.not_to raise_error
810
+ end
811
+
812
+ it 'writes a error via context and fails to apply when all retry attempts used' do
813
+ expect(ps_manager).to receive(:execute).and_return({ stdout: '{"errormessage": "The Invoke-DscResource cmdlet is in progress and must return before Invoke-DscResource can be invoked"}' })
814
+ .exactly(5).times
815
+ expect(context).to receive(:notice).with(/Invoke-DscResource collision detected: Please stagger the timing of your Puppet runs as this can lead to unexpected behaviour./).once
816
+ expect(context).to receive(:notice).with('Sleeping for 60 seconds.').exactly(5).times
817
+ expect(context).to receive(:notice).with(/Retrying: attempt [1-5] of 5/).exactly(5).times
818
+ expect(ps_manager).to receive(:execute).and_return({ stdout: '{"errormessage": "The Invoke-DscResource cmdlet is in progress and must return before Invoke-DscResource can be invoked"}' })
819
+ expect(context).to receive(:notice).with(/Attempt [1-5] of 5 failed/).exactly(5).times
820
+ expect(context).to receive(:err).with(/The Invoke-DscResource cmdlet is in progress and must return before Invoke-DscResource can be invoked/)
821
+ allow(provider).to receive(:sleep)
822
+ expect(result).to be_nil
823
+ end
824
+
825
+ it 'writes an error via context and fails to apply when encountering an unexpected error' do
826
+ expect(ps_manager).to receive(:execute).and_return({ stdout: '{"errormessage": "The Invoke-DscResource cmdlet is in progress and must return before Invoke-DscResource can be invoked"}' })
827
+ expect(context).to receive(:notice).with(/Invoke-DscResource collision detected: Please stagger the timing of your Puppet runs as this can lead to unexpected behaviour./).once
828
+ expect(context).to receive(:notice).with('Sleeping for 60 seconds.').once
829
+ expect(context).to receive(:notice).with(/Retrying: attempt 1 of 5/).once
830
+ allow(provider).to receive(:sleep)
831
+ expect(ps_manager).to receive(:execute).and_return({ stdout: '{"errormessage": "Some unexpected error"}' }).once
832
+ expect(context).to receive(:notice).with(/Attempt 1 of 5 failed/).once
833
+ expect(context).to receive(:err).with(/Some unexpected error/)
834
+ expect(result).to be_nil
835
+ end
836
+ end
837
+
793
838
  context 'when the invocation script returns data without errors' do
794
839
  it 'filters for the correct properties to invoke and returns the results' do
795
840
  expect(ps_manager).to receive(:execute).with("Script: #{apply_props}").and_return({ stdout: '{"in_desired_state": true, "errormessage": null}' })
@@ -981,11 +1026,11 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
981
1026
  end
982
1027
 
983
1028
  describe '.invoke_test_method' do
984
- subject(:result) { provider.invoke_test_method(context, name, expect(subject).to) }
1029
+ subject(:result) { provider.invoke_test_method(context, name, should_hash) }
985
1030
 
986
1031
  let(:name) { { name: 'foo', dsc_name: 'bar' } }
987
- let(:should) { name.merge(dsc_ensure: 'present') }
988
- let(:test_properties) { expect(subject).to.reject { |k, _v| k == :name } }
1032
+ let(:should_hash) { name.merge(dsc_ensure: 'present') }
1033
+ let(:test_properties) { should_hash.reject { |k, _v| k == :name } }
989
1034
  let(:invoke_dsc_resource_data) { nil }
990
1035
 
991
1036
  before do
@@ -995,7 +1040,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
995
1040
  end
996
1041
 
997
1042
  after do
998
- described_class.instance_variable_set(:@cached_test_results, [])
1043
+ provider.instance_variable_set(:@cached_test_results, [])
999
1044
  end
1000
1045
 
1001
1046
  context 'when something went wrong calling Invoke-DscResource' do
@@ -1043,7 +1088,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1043
1088
 
1044
1089
  describe '.instantiated_variables' do
1045
1090
  after do
1046
- described_class.instance_variable_set(:@instantiated_variables, [])
1091
+ provider.instance_variable_set(:@instantiated_variables, [])
1047
1092
  end
1048
1093
 
1049
1094
  it 'sets the instantiated_variables instance variable to {} if not initialized' do
@@ -1051,20 +1096,20 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1051
1096
  end
1052
1097
 
1053
1098
  it 'returns the instantiated_variables instance variable if already initialized' do
1054
- described_class.instance_variable_set(:@instantiated_variables, { foo: 'bar' })
1099
+ provider.instance_variable_set(:@instantiated_variables, { foo: 'bar' })
1055
1100
  expect(provider.instantiated_variables).to eq({ foo: 'bar' })
1056
1101
  end
1057
1102
  end
1058
1103
 
1059
1104
  describe '.clear_instantiated_variables!' do
1060
1105
  after do
1061
- described_class.instance_variable_set(:@instantiated_variables, [])
1106
+ provider.instance_variable_set(:@instantiated_variables, [])
1062
1107
  end
1063
1108
 
1064
1109
  it 'sets the instantiated_variables instance variable to {}' do
1065
- described_class.instance_variable_set(:@instantiated_variables, { foo: 'bar' })
1110
+ provider.instance_variable_set(:@instantiated_variables, { foo: 'bar' })
1066
1111
  expect { provider.clear_instantiated_variables! }.not_to raise_error
1067
- expect(described_class.instance_variable_get(:@instantiated_variables)).to eq({})
1112
+ expect(provider.instance_variable_get(:@instantiated_variables)).to eq({})
1068
1113
  end
1069
1114
  end
1070
1115
 
@@ -1087,16 +1132,16 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1087
1132
  end
1088
1133
 
1089
1134
  after do
1090
- described_class.instance_variable_set(:@logon_failures, [])
1135
+ provider.instance_variable_set(:@logon_failures, [])
1091
1136
  end
1092
1137
 
1093
1138
  it 'returns false if there have been no failed logons with the username/password combination' do
1094
- described_class.instance_variable_set(:@logon_failures, [bad_credential_hash])
1139
+ provider.instance_variable_set(:@logon_failures, [bad_credential_hash])
1095
1140
  expect(provider.logon_failed_already?(good_credential_hash)).to be(false)
1096
1141
  end
1097
1142
 
1098
1143
  it 'returns true if the username/password specified are found in the logon_failures instance variable' do
1099
- described_class.instance_variable_set(:@logon_failures, [good_credential_hash, bad_credential_hash])
1144
+ provider.instance_variable_set(:@logon_failures, [good_credential_hash, bad_credential_hash])
1100
1145
  expect(provider.logon_failed_already?(bad_credential_hash)).to be(true)
1101
1146
  end
1102
1147
  end
@@ -1437,16 +1482,18 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1437
1482
  context 'when the resource does not have the dscmeta_resource_implementation key' do
1438
1483
  let(:test_resource) { {} }
1439
1484
 
1440
- it 'returns nil' do
1441
- expect(result).to be_nil
1485
+ it 'sets $UnmungedPSModulePath to the current PSModulePath' do
1486
+ # since https://github.com/puppetlabs/ruby-pwsh/pull/261 we load vendored path for MOF resources as well
1487
+ expect(result).to match(/\$UnmungedPSModulePath = .+GetEnvironmentVariable.+PSModulePath.+machine/)
1442
1488
  end
1443
1489
  end
1444
1490
 
1445
1491
  context "when the resource's dscmeta_resource_implementation is not 'Class'" do
1446
1492
  let(:test_resource) { { dscmeta_resource_implementation: 'MOF' } }
1447
1493
 
1448
- it 'returns nil' do
1449
- expect(result).to be_nil
1494
+ # since https://github.com/puppetlabs/ruby-pwsh/pull/261 we load vendored path for MOF resources as well
1495
+ it 'sets $UnmungedPSModulePath to the current PSModulePath' do
1496
+ expect(result).to match(/\$UnmungedPSModulePath = .+GetEnvironmentVariable.+PSModulePath.+machine/)
1450
1497
  end
1451
1498
  end
1452
1499
 
@@ -1510,7 +1557,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1510
1557
  end
1511
1558
 
1512
1559
  after do
1513
- described_class.instance_variable_set(:@instantiated_variables, [])
1560
+ provider.instance_variable_set(:@instantiated_variables, [])
1514
1561
  end
1515
1562
 
1516
1563
  it 'writes the ruby representation of the credentials as the value of a key named for the new variable into the instantiated_variables cache' do
@@ -1543,7 +1590,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1543
1590
  subject(:result) { provider.prepare_cim_instances(test_resource) }
1544
1591
 
1545
1592
  after do
1546
- described_class.instance_variable_set(:@instantiated_variables, [])
1593
+ provider.instance_variable_set(:@instantiated_variables, [])
1547
1594
  end
1548
1595
 
1549
1596
  context 'when a cim instance is passed without nested cim instances' do
@@ -1652,7 +1699,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1652
1699
 
1653
1700
  describe '.format_ciminstance' do
1654
1701
  after do
1655
- described_class.instance_variable_set(:@instantiated_variables, [])
1702
+ provider.instance_variable_set(:@instantiated_variables, [])
1656
1703
  end
1657
1704
 
1658
1705
  it 'defines and returns a new cim instance as a PowerShell variable, passing the class name and property hash' do
@@ -1668,7 +1715,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
1668
1715
  end
1669
1716
 
1670
1717
  it 'interpolates variables in the case of a cim instance containing a nested instance' do
1671
- described_class.instance_variable_set(:@instantiated_variables, { 'SomeVariable' => { 'bar' => 'ope' } })
1718
+ provider.instance_variable_set(:@instantiated_variables, { 'SomeVariable' => { 'bar' => 'ope' } })
1672
1719
  property_hash = { 'foo' => { 'bar' => 'ope' } }
1673
1720
  expect(provider.format_ciminstance('foo', 'SomeClass', property_hash)).to match(/@\{'foo' = \$SomeVariable\}/)
1674
1721
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-pwsh
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-13 00:00:00.000000000 Z
11
+ date: 2024-02-21 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: PowerShell code manager for ruby.
14
14
  email: