Class: PuppetX::Dsc::PowerShellManager
- Inherits:
-
Object
- Object
- PuppetX::Dsc::PowerShellManager
- Defined in:
- lib/puppet_x/puppetlabs/powershell_manager.rb
Constant Summary collapse
- @@instances =
{}
Class Method Summary collapse
- .compatible_version_of_powershell? ⇒ Boolean
- .init_path ⇒ Object
- .instance(cmd, debug = false, pipe_timeout = 180) ⇒ Object
- .supported? ⇒ Boolean
- .win32console_enabled? ⇒ Boolean
Instance Method Summary collapse
- #alive? ⇒ Boolean
- #execute(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
- #exit ⇒ Object
-
#initialize(cmd, debug, pipe_timeout) ⇒ PowerShellManager
constructor
A new instance of PowerShellManager.
- #invalid_lib_paths?(path_collection) ⇒ Boolean
- #make_ps_code(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
Constructor Details
#initialize(cmd, debug, pipe_timeout) ⇒ PowerShellManager
Returns a new instance of PowerShellManager.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 55 def initialize(cmd, debug, pipe_timeout) @usable = true named_pipe_name = "#{SecureRandom.uuid}PuppetPsHost" raise "Bad configuration for ENV['lib']=#{ENV['lib']} - invalid path" if invalid_lib_paths?(ENV['lib']) ps_args = ['-File', self.class.init_path, "\"#{named_pipe_name}\""] ps_args << '"-EmitDebugOutput"' if debug # @stderr should never be written to as PowerShell host redirects output stdin, @stdout, @stderr, @ps_process = Open3.popen3("#{cmd} #{ps_args.join(' ')}") stdin.close Puppet.debug "#{Time.now} #{cmd} is running as pid: #{@ps_process[:pid]}" pipe_path = "\\\\.\\pipe\\#{named_pipe_name}" # wait for the pipe server to signal ready, and fail if no response in 10 seconds # wait up to 180 seconds in 0.2 second intervals to be able to open the pipe # If the pipe_timeout is ever specified as less than the sleep interval it will # never try to connect to a pipe and error out as if a timeout occurred. sleep_interval = 0.2 (pipe_timeout / sleep_interval).to_int.times do begin # pipe is opened in binary mode and must always @pipe = File.open(pipe_path, 'r+b') break rescue sleep sleep_interval end end if @pipe.nil? # Tear down and kill the process if unable to connect to the pipe; failure to do so # results in zombie processes being left after the puppet run. We discovered that # Closing @ps_process via .kill instead of using this method actually kills the watcher # and leaves an orphaned process behind. Failing to close stdout and stderr also leaves # clutter behind, so explicitly close those too. @stdout.close if !@stdout.closed? @stderr.close if !@stderr.closed? Process.kill("KILL", @ps_process[:pid]) if @ps_process.alive? raise "Failure waiting for PowerShell process #{@ps_process[:pid]} to start pipe server" end Puppet.debug "#{Time.now} PowerShell initialization complete for pid: #{@ps_process[:pid]}" at_exit { exit } end |
Class Method Details
.compatible_version_of_powershell? ⇒ Boolean
31 32 33 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 31 def self.compatible_version_of_powershell? @compatible_powershell_version ||= PuppetX::PuppetLabs::Dsc::CompatiblePowerShellVersion.compatible_version? end |
.init_path ⇒ Object
149 150 151 152 153 154 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 149 def self.init_path # a PowerShell -File compatible path to bootstrap the instance path = File.('../../templates/dsc', __FILE__) path = File.join(path, 'init_ps.ps1').gsub('/', '\\') "\"#{path}\"" end |
.instance(cmd, debug = false, pipe_timeout = 180) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 12 def self.instance(cmd, debug = false, pipe_timeout = 180) key = cmd + debug.to_s manager = @@instances[key] if manager.nil? || !manager.alive? # ignore any errors trying to tear down this unusable instance manager.exit if manager rescue nil @@instances[key] = PowerShellManager.new(cmd, debug, pipe_timeout) end @@instances[key] end |
.supported? ⇒ Boolean
35 36 37 38 39 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 35 def self.supported? Puppet::Util::Platform.windows? && compatible_version_of_powershell? && !win32console_enabled? end |
.win32console_enabled? ⇒ Boolean
25 26 27 28 29 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 25 def self.win32console_enabled? @win32console_enabled ||= defined?(Win32) && defined?(Win32::Console) && Win32::Console.class == Class end |
Instance Method Details
#alive? ⇒ Boolean
102 103 104 105 106 107 108 109 110 111 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 102 def alive? # powershell process running @ps_process.alive? && # explicitly set during a read / write failure, like broken pipe EPIPE @usable && # an explicit failure state might not have been hit, but IO may be closed self.class.is_stream_valid?(@pipe) && self.class.is_stream_valid?(@stdout) && self.class.is_stream_valid?(@stderr) end |
#execute(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 113 def execute(powershell_code, timeout_ms = nil, working_dir = nil) code = make_ps_code(powershell_code, timeout_ms, working_dir) # err is drained stderr pipe (not captured by redirection inside PS) # or during a failure, a Ruby callstack array out, native_stdout, err = exec_read_result(code) # an error was caught during execution that has invalidated any results return { :exitcode => -1, :stderr => err } if !@usable && out.nil? out[:exitcode] = out[:exitcode].to_i if !out[:exitcode].nil? # if err contains data it must be "real" stderr output # which should be appended to what PS has already captured out[:stderr] = out[:stderr].nil? ? [] : [out[:stderr]] out[:stderr] += err if !err.nil? out[:native_stdout] = native_stdout out end |
#exit ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 134 def exit @usable = false Puppet.debug "PowerShellManager exiting..." # pipe may still be open, but if stdout / stderr are dead PS process is in trouble # and will block forever on a write to the pipe # its safer to close pipe on Ruby side, which gracefully shuts down PS side @pipe.close if !@pipe.closed? @stdout.close if !@stdout.closed? @stderr.close if !@stderr.closed? # wait up to 2 seconds for the watcher thread to fully exit @ps_process.join(2) end |
#invalid_lib_paths?(path_collection) ⇒ Boolean
41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 41 def invalid_lib_paths?(path_collection) invalid_paths = false return invalid_paths if path_collection.nil? || path_collection.empty? path_collection.split(';').each do |path| unless File.directory?(path) invalid_paths = true end end invalid_paths end |
#make_ps_code(powershell_code, timeout_ms = nil, working_dir = nil) ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/puppet_x/puppetlabs/powershell_manager.rb', line 156 def make_ps_code(powershell_code, timeout_ms = nil, working_dir = nil) begin timeout_ms = Integer(timeout_ms) # Lower bound protection. The polling resolution is only 50ms if (timeout_ms < 50) then timeout_ms = 50 end rescue timeout_ms = 300 * 1000 end # PS side expects Invoke-PowerShellUserCode is always the return value here <<-CODE $params = @{ Code = @' #{powershell_code} '@ TimeoutMilliseconds = #{timeout_ms} WorkingDirectory = "#{working_dir}" } Invoke-PowerShellUserCode @params CODE end |