ruby-pwsh 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: