ruby-pwsh 1.1.1 → 1.2.0

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: 1ac282abeea132104fda1b3fc2f4743f4b99603cdd6b73a42b7063641c9e65d9
4
- data.tar.gz: 410bc1d7a7b08366a89019324b172219102197f8aa6891bcfaa1729e3bd76925
3
+ metadata.gz: e217873cdddb9b32ba467e0a9f5de6977c04b11a1c0788613dda6181e96a62bd
4
+ data.tar.gz: 8dc82dfad8869d7c632ba3d902b4e22c375aeb5988f84e359269163da885b604
5
5
  SHA512:
6
- metadata.gz: 0fd8c7af3f87ce61121a7404b66cfae5cf78fea8bdb885dc7715467bb5092bd974a9fd4d7f94824fe10675d56b4e4dc7b439826f95bc5d9672f14fcb74692501
7
- data.tar.gz: 198328e45920aa2794cab2984e090c14c81ce56b4b038e4ae0b7f22ad66d327d0bbd34b5726709108c14609324dba1687a83a2f67026a2d8abf12d749067ca27
6
+ metadata.gz: a7e02f1cee48fa49316c133268a16f9bc3b8f195927834854c81eee134cdb7056710cd1be04eaf992ca036aa2b014175d06ebf8684eef6354ef8d10a07a62e31
7
+ data.tar.gz: da78f276002b67faf3716ce0b8157fe3cf0edbbeb5da3ae1145c3ed696e386838285138fdd0c75b8a592b677b7631c66ce07355a25788dee58354af0ecdd9eaa
data/README.md CHANGED
@@ -86,6 +86,10 @@ The following platforms are supported:
86
86
  - RedHat
87
87
  - Ubuntu
88
88
 
89
+ ## Limitations
90
+
91
+ - When PowerShell [Script Block Logging](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging_windows?view=powershell-7.4#enabling-script-block-logging) is enabled, data marked as sensitive in your manifest may appear in these logs as plain text. It is **highly recommended**, by both Puppet and Microsoft, that you also enable [Protected Event Logging](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging_windows?view=powershell-7.4#protected-event-logging) alongside this to encrypt the logs to protect this information.
92
+
89
93
  ## License
90
94
 
91
95
  This codebase is licensed under Apache 2.0. However, the open source dependencies included in this codebase might be subject to other software licenses such as AGPL, GPL2.0, and MIT.
@@ -15,6 +15,7 @@ class Puppet::Provider::DscBaseProvider # rubocop:disable Metrics/ClassLength
15
15
  @cached_query_results = []
16
16
  @cached_test_results = []
17
17
  @logon_failures = []
18
+ @timeout = nil # default timeout, ps_manager.execute is expecting nil by default..
18
19
  super
19
20
  end
20
21
 
@@ -251,18 +252,22 @@ class Puppet::Provider::DscBaseProvider # rubocop:disable Metrics/ClassLength
251
252
  context.err('Logon credentials are invalid')
252
253
  return nil
253
254
  end
255
+ specify_dsc_timeout(name_hash)
254
256
  resource = invocable_resource(props, context, method)
255
257
  script_content = ps_script_content(resource)
258
+ context.debug("Invoke-DSC Timeout: #{@timeout} milliseconds") if @timeout
256
259
  context.debug("Script:\n #{redact_secrets(script_content)}")
257
- output = ps_manager.execute(remove_secret_identifiers(script_content))[:stdout]
260
+ output = ps_manager.execute(remove_secret_identifiers(script_content), @timeout)
258
261
 
259
- if output.nil?
260
- context.err('Nothing returned')
262
+ if output[:stdout].nil?
263
+ message = 'Nothing returned.'
264
+ message += " #{output[:errormessage]}" if output[:errormessage]&.match?(/PowerShell module timeout \(\d+ ms\) exceeded while executing/)
265
+ context.err(message)
261
266
  return nil
262
267
  end
263
268
 
264
269
  begin
265
- data = JSON.parse(output)
270
+ data = JSON.parse(output[:stdout])
266
271
  rescue StandardError => e
267
272
  context.err(e)
268
273
  return nil
@@ -295,6 +300,18 @@ class Puppet::Provider::DscBaseProvider # rubocop:disable Metrics/ClassLength
295
300
  data
296
301
  end
297
302
 
303
+ # Sets the @timeout instance variable.
304
+ # @param name_hash [Hash] the hash of namevars to be passed as properties to `Invoke-DscResource`
305
+ # The @timeout variable is set to the value of name_hash[:dsc_timeout] in milliseconds
306
+ # If name_hash[:dsc_timeout] is nil, @timeout is not changed.
307
+ # If @timeout is already set to a value other than nil,
308
+ # it is changed only if it's different from name_hash[:dsc_timeout]..
309
+ def specify_dsc_timeout(name_hash)
310
+ return unless name_hash[:dsc_timeout] && (@timeout.nil? || @timeout != name_hash[:dsc_timeout])
311
+
312
+ @timeout = name_hash[:dsc_timeout] * 1000
313
+ end
314
+
298
315
  # Retries Invoke-DscResource when returned error matches error regex supplied as param.
299
316
  # @param context [Object] the Puppet runtime context to operate in and send feedback to
300
317
  # @param max_retry_count [Int] max number of times to retry Invoke-DscResource
@@ -665,16 +682,16 @@ class Puppet::Provider::DscBaseProvider # rubocop:disable Metrics/ClassLength
665
682
  context.type.attributes.select { |_attribute, properties| properties[:mandatory_for_set] }.keys
666
683
  end
667
684
 
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
685
+ # Parses the DSC resource type definition to retrieve the names of any attributes which are specified as required strings
686
+ # This is used to ensure that any nil values are converted to empty strings to match puppets expected value
670
687
  # @param context [Object] the Puppet runtime context to operate in and send feedback to
671
688
  # @param data [Hash] the hash of properties returned from the DSC resource
672
689
  # @return [Hash] returns a data hash with any nil values converted to empty strings
673
690
  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)
691
+ nil_attributes = data.select { |_name, value| value.nil? }.keys
692
+ nil_attributes.each do |nil_attr|
693
+ attribute_type = context.type.attributes[nil_attr][:type]
694
+ data[nil_attr] = '' if (attribute_type.include?('Enum[') && enum_values(context, nil_attr).include?('')) || attribute_type == 'String'
678
695
  end
679
696
  data
680
697
  end
@@ -1076,6 +1093,10 @@ class Puppet::Provider::DscBaseProvider # rubocop:disable Metrics/ClassLength
1076
1093
  def ps_manager
1077
1094
  debug_output = Puppet::Util::Log.level == :debug
1078
1095
  # TODO: Allow you to specify an alternate path, either to pwsh generally or a specific pwsh path.
1079
- Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args, debug: debug_output)
1096
+ if Pwsh::Util.on_windows?
1097
+ Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args, debug: debug_output)
1098
+ else
1099
+ Pwsh::Manager.instance(Pwsh::Manager.pwsh_path, Pwsh::Manager.pwsh_args, debug: debug_output)
1100
+ end
1080
1101
  end
1081
1102
  end
@@ -123,4 +123,4 @@ Function ConvertTo-CanonicalResult {
123
123
 
124
124
  # Output the final result
125
125
  $ResultObject
126
- }
126
+ }
data/lib/pwsh/util.rb CHANGED
@@ -7,12 +7,12 @@ module Pwsh
7
7
  module_function
8
8
 
9
9
  # Verifies whether or not the current context is running on a Windows node.
10
+ # Implementation copied from `facets`: https://github.com/rubyworks/facets/blob/main/lib/standard/facets/rbconfig.rb
10
11
  #
11
12
  # @return [Bool] true if on windows
12
13
  def on_windows?
13
- # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
14
- # library uses that to test what platform it's on.
15
- !!File::ALT_SEPARATOR
14
+ host_os = RbConfig::CONFIG['host_os']
15
+ !!(host_os =~ /mswin|mingw/)
16
16
  end
17
17
 
18
18
  # Verify paths specified are valid directories which exist.
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.1.1'
5
+ VERSION = '1.2.0'
6
6
  end
data/lib/pwsh.rb CHANGED
@@ -380,7 +380,7 @@ module Pwsh
380
380
  pwsh_paths << File.join(path, 'pwsh.exe') if File.exist?(File.join(path, 'pwsh.exe'))
381
381
  end
382
382
  else
383
- search_paths.split(File::PATH_SEPARATOR).each do |path|
383
+ search_paths.split(':').each do |path|
384
384
  pwsh_paths << File.join(path, 'pwsh') if File.exist?(File.join(path, 'pwsh'))
385
385
  end
386
386
  end
@@ -541,7 +541,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
541
541
 
542
542
  context 'when the invocation script returns data without errors' do
543
543
  before do
544
- allow(ps_manager).to receive(:execute).with(script).and_return({ stdout: 'DSC Data' })
544
+ allow(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: 'DSC Data' })
545
545
  allow(JSON).to receive(:parse).with('DSC Data').and_return(parsed_invocation_data)
546
546
  allow(Puppet::Pops::Time::Timestamp).to receive(:parse).with('2100-01-01').and_return('TimeStamp:2100-01-01')
547
547
  allow(provider).to receive(:fetch_cached_hashes).and_return([])
@@ -659,15 +659,15 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
659
659
  context 'when the DSC invocation errors' do
660
660
  it 'writes an error and returns nil' do
661
661
  expect(provider).not_to receive(:logon_failed_already?)
662
- expect(ps_manager).to receive(:execute).with(script).and_return({ stdout: nil })
663
- expect(context).to receive(:err).with('Nothing returned')
662
+ expect(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: nil })
663
+ expect(context).to receive(:err).with('Nothing returned.')
664
664
  expect(result).to be_nil
665
665
  end
666
666
  end
667
667
 
668
668
  context 'when handling DateTimes' do
669
669
  before do
670
- allow(ps_manager).to receive(:execute).with(script).and_return({ stdout: 'DSC Data' })
670
+ allow(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: 'DSC Data' })
671
671
  allow(JSON).to receive(:parse).with('DSC Data').and_return(parsed_invocation_data)
672
672
  allow(provider).to receive(:fetch_cached_hashes).and_return([])
673
673
  end
@@ -719,7 +719,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
719
719
  context 'when the credential is invalid' do
720
720
  before do
721
721
  allow(provider).to receive(:logon_failed_already?).and_return(false)
722
- allow(ps_manager).to receive(:execute).with(script).and_return({ stdout: 'DSC Data' })
722
+ allow(ps_manager).to receive(:execute).with(script, nil).and_return({ stdout: 'DSC Data' })
723
723
  allow(JSON).to receive(:parse).with('DSC Data').and_return({ 'errormessage' => dsc_logon_failure_error })
724
724
  allow(context).to receive(:err).with(name_hash[:name], puppet_logon_failure_error)
725
725
  end
@@ -783,7 +783,7 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
783
783
  context 'when the invocation script returns nil' do
784
784
  it 'errors via context but does not raise' do
785
785
  expect(ps_manager).to receive(:execute).and_return({ stdout: nil })
786
- expect(context).to receive(:err).with('Nothing returned')
786
+ expect(context).to receive(:err).with('Nothing returned.')
787
787
  expect { result }.not_to raise_error
788
788
  end
789
789
  end
@@ -835,9 +835,29 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
835
835
  end
836
836
  end
837
837
 
838
+ context 'when a dsc_timeout is specified' do
839
+ let(:should_hash) { name.merge(dsc_timeout: 5) }
840
+ let(:apply_props_with_timeout) { { dsc_name: 'foo', dsc_timeout: 5 } }
841
+ let(:resource_with_timeout) { "Resource: #{apply_props_with_timeout}" }
842
+ let(:script_with_timeout) { "Script: #{apply_props_with_timeout}" }
843
+
844
+ before do
845
+ allow(provider).to receive(:invocable_resource).with(apply_props_with_timeout, context, 'set').and_return(resource_with_timeout)
846
+ allow(provider).to receive(:ps_script_content).with(resource_with_timeout).and_return(script_with_timeout)
847
+ allow(provider).to receive(:remove_secret_identifiers).with(script_with_timeout).and_return(script_with_timeout)
848
+ end
849
+
850
+ it 'sets @timeout and passes it to ps_manager.execute' do
851
+ provider.instance_variable_set(:@timeout, nil)
852
+ expect(ps_manager).to receive(:execute).with(script_with_timeout, 5000).and_return({ stdout: '{"in_desired_state": true, "errormessage": null}' })
853
+ provider.invoke_set_method(context, name, should_hash)
854
+ expect(provider.instance_variable_get(:@timeout)).to eq(5000)
855
+ end
856
+ end
857
+
838
858
  context 'when the invocation script returns data without errors' do
839
859
  it 'filters for the correct properties to invoke and returns the results' do
840
- expect(ps_manager).to receive(:execute).with("Script: #{apply_props}").and_return({ stdout: '{"in_desired_state": true, "errormessage": null}' })
860
+ expect(ps_manager).to receive(:execute).with("Script: #{apply_props}", nil).and_return({ stdout: '{"in_desired_state": true, "errormessage": null}' })
841
861
  expect(context).not_to receive(:err)
842
862
  expect(result).to eq({ 'in_desired_state' => true, 'errormessage' => nil })
843
863
  end
@@ -2110,21 +2130,44 @@ RSpec.describe Puppet::Provider::DscBaseProvider do
2110
2130
  end
2111
2131
 
2112
2132
  describe '.ps_manager' do
2113
- before do
2114
- allow(Pwsh::Manager).to receive(:powershell_path).and_return('pwsh')
2115
- allow(Pwsh::Manager).to receive(:powershell_args).and_return('args')
2116
- end
2133
+ describe '.ps_manager on non-Windows' do
2134
+ before do
2135
+ allow(Pwsh::Util).to receive(:on_windows?).and_return(false)
2136
+ allow(Pwsh::Manager).to receive(:pwsh_path).and_return('pwsh')
2137
+ allow(Pwsh::Manager).to receive(:pwsh_args).and_return('args')
2138
+ end
2139
+
2140
+ it 'Initializes an instance of the Pwsh::Manager' do
2141
+ expect(Puppet::Util::Log).to receive(:level).and_return(:normal)
2142
+ expect(Pwsh::Manager).to receive(:instance).with('pwsh', 'args', debug: false)
2143
+ expect { provider.ps_manager }.not_to raise_error
2144
+ end
2117
2145
 
2118
- it 'Initializes an instance of the Pwsh::Manager' do
2119
- expect(Puppet::Util::Log).to receive(:level).and_return(:normal)
2120
- expect(Pwsh::Manager).to receive(:instance).with('pwsh', 'args', debug: false)
2121
- expect { provider.ps_manager }.not_to raise_error
2146
+ it 'passes debug as true if Puppet::Util::Log.level is debug' do
2147
+ expect(Puppet::Util::Log).to receive(:level).and_return(:debug)
2148
+ expect(Pwsh::Manager).to receive(:instance).with('pwsh', 'args', debug: true)
2149
+ expect { provider.ps_manager }.not_to raise_error
2150
+ end
2122
2151
  end
2123
2152
 
2124
- it 'passes debug as true if Puppet::Util::Log.level is debug' do
2125
- expect(Puppet::Util::Log).to receive(:level).and_return(:debug)
2126
- expect(Pwsh::Manager).to receive(:instance).with('pwsh', 'args', debug: true)
2127
- expect { provider.ps_manager }.not_to raise_error
2153
+ describe '.ps_manager on Windows' do
2154
+ before do
2155
+ allow(Pwsh::Util).to receive(:on_windows?).and_return(true)
2156
+ allow(Pwsh::Manager).to receive(:powershell_path).and_return('pwsh')
2157
+ allow(Pwsh::Manager).to receive(:powershell_args).and_return('args')
2158
+ end
2159
+
2160
+ it 'Initializes an instance of the Pwsh::Manager' do
2161
+ expect(Puppet::Util::Log).to receive(:level).and_return(:normal)
2162
+ expect(Pwsh::Manager).to receive(:instance).with('pwsh', 'args', debug: false)
2163
+ expect { provider.ps_manager }.not_to raise_error
2164
+ end
2165
+
2166
+ it 'passes debug as true if Puppet::Util::Log.level is debug' do
2167
+ expect(Puppet::Util::Log).to receive(:level).and_return(:debug)
2168
+ expect(Pwsh::Manager).to receive(:instance).with('pwsh', 'args', debug: true)
2169
+ expect { provider.ps_manager }.not_to raise_error
2170
+ end
2128
2171
  end
2129
2172
  end
2130
2173
  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.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-21 00:00:00.000000000 Z
11
+ date: 2024-08-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: PowerShell code manager for ruby.
14
14
  email: