class Kitchen::Transport::Winrm::Connection
A Connection
instance can be generated and re-generated, given new connection details such as connection port, hostname, credentials, etc. This object is responsible for carrying out the actions on the remote host such as executing commands, transferring files, etc.
@author Fletcher Nichol <fnichol@nichol.ca>
Constants
- PING_COMMAND
- RESCUE_EXCEPTIONS_ON_ESTABLISH
Attributes
@return [Integer] how many times to retry when failing to execute
a command or transfer files
@api private
@return [Float] how many seconds to wait before attempting a retry
when failing to execute a command or transfer files
@api private
@return [Boolean] whether to use winrm-elevated for running commands @api private
@return [String] display name for the associated instance @api private
@return [String] local path to the root of the project @api private
@return [Integer] how many times to retry when invoking
`#wait_until_ready` before failing
@api private
@return [Integer] the TCP port number to use when connection to the
remote WinRM host
@api private
Public Class Methods
(see Base::Connection#initialize)
Kitchen::Transport::Base::Connection::new
# File lib/kitchen/transport/winrm.rb, line 86 def initialize(config = {}) super(config) @unelevated_session = nil @elevated_session = nil end
Public Instance Methods
(see Base::Connection#close
)
# File lib/kitchen/transport/winrm.rb, line 93 def close @unelevated_session.close if @unelevated_session @elevated_session.close if @elevated_session ensure @unelevated_session = nil @elevated_session = nil @file_transporter = nil end
(see Base::Connection#download
)
# File lib/kitchen/transport/winrm.rb, line 142 def download(remotes, local) # ensure the parent dir of the local target exists FileUtils.mkdir_p(File.dirname(local)) Array(remotes).each do |remote| file_manager.download(remote, local) end end
(see Base::Connection#execute
)
# File lib/kitchen/transport/winrm.rb, line 103 def execute(command) return if command.nil? logger.debug("[WinRM] #{self} (#{command})") exit_code, stderr = execute_with_exit_code(command) if logger.debug? && exit_code == 0 log_stderr_on_warn(stderr) elsif exit_code != 0 log_stderr_on_warn(stderr) raise Transport::WinrmFailed.new( "WinRM exited (#{exit_code}) for command: [#{command}]", exit_code ) end end
@return [Winrm::FileManager] a file transporter @api private
# File lib/kitchen/transport/winrm.rb, line 153 def file_manager @file_manager ||= WinRM::FS::FileManager.new(connection) end
(see Base::Connection#login_command
)
# File lib/kitchen/transport/winrm.rb, line 122 def login_command case RbConfig::CONFIG["host_os"] when /darwin/ login_command_for_mac when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ login_command_for_windows when /linux/ login_command_for_linux else raise ActionFailed, "Remote login not supported in #{self.class} " \ "from host OS '#{RbConfig::CONFIG["host_os"]}'." end end
(see Base::Connection#upload
)
# File lib/kitchen/transport/winrm.rb, line 137 def upload(locals, remote) file_transporter.upload(locals, remote) end
(see Base::Connection#wait_until_ready
)
# File lib/kitchen/transport/winrm.rb, line 158 def wait_until_ready delay = 3 unelevated_session( retry_limit: max_wait_until_ready / delay, retry_delay: delay ) execute(PING_COMMAND.dup) rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e retries ||= connection_retries.to_i raise e if (retries -= 1) < 0 logger.debug("[WinRM] PING_COMMAND failed. Retrying...") logger.debug("#{e.class}::#{e.message}") sleep(connection_retry_sleep.to_i) retry end
Private Instance Methods
Creates a winrm Connection
instance
@param retry_options [Hash] retry options for the initial connection @return [Winrm::Connection] the winrm connection @api private
# File lib/kitchen/transport/winrm.rb, line 379 def connection(retry_options = {}) @connection ||= begin opts = { retry_limit: connection_retries.to_i, retry_delay: connection_retry_sleep.to_i, }.merge(retry_options) ::WinRM::Connection.new(options.merge(opts)).tap do |conn| conn.logger = logger end end end
Writes an RDP document to the local file system.
@param opts [Hash] file options @option opts [true,false] :mac whether or not the document is for a
Mac system
@api private
# File lib/kitchen/transport/winrm.rb, line 223 def create_rdp_doc(opts = {}) content = Util.outdent!(<<-RDP) full address:s:#{URI.parse(options[:endpoint]).host}:#{rdp_port} prompt for credentials:i:1 username:s:#{options[:user]} RDP content.prepend("drivestoredirect:s:*\n") if opts[:mac] File.open(rdp_doc_path, "wb") { |f| f.write(content) } if logger.debug? debug("Creating RDP document for #{instance_name} (#{rdp_doc_path})") debug("------------") IO.read(rdp_doc_path).each_line { |l| debug(l.chomp.to_s) } debug("------------") end end
Creates an elevated session for running commands via a scheduled task
@return [Winrm::Shells::Elevated] the elevated shell @api private
# File lib/kitchen/transport/winrm.rb, line 367 def elevated_session(retry_options = {}) @elevated_session ||= connection(retry_options).shell(:elevated).tap do |shell| shell.username = options[:elevated_username] shell.password = options[:elevated_password] end end
Execute a Powershell script over WinRM and return the command's exit code and standard error.
@param command [String] Powershell script to execute @return [[Integer,String]] an array containing the exit code of the
script and the standard error stream
@api private
# File lib/kitchen/transport/winrm.rb, line 248 def execute_with_exit_code(command) if elevated session = elevated_session command = "$env:temp='#{unelevated_temp_dir}';#{command}" else session = unelevated_session end begin response = session.run(command) do |stdout, _| logger << stdout if stdout end [response.exitcode, response.stderr] ensure close end end
@return [Winrm::FileTransporter] a file transporter @api private
# File lib/kitchen/transport/winrm.rb, line 272 def file_transporter @file_transporter ||= WinRM::FS::Core::FileTransporter.new(unelevated_session) end
(see Base#init_options)
Kitchen::Transport::Base::Connection#init_options
# File lib/kitchen/transport/winrm.rb, line 277 def init_options(options) super @instance_name = @options.delete(:instance_name) @kitchen_root = @options.delete(:kitchen_root) @rdp_port = @options.delete(:rdp_port) @connection_retries = @options.delete(:connection_retries) @connection_retry_sleep = @options.delete(:connection_retry_sleep) @operation_timeout = @options.delete(:operation_timeout) @receive_timeout = @options.delete(:receive_timeout) @max_wait_until_ready = @options.delete(:max_wait_until_ready) @elevated = @options.delete(:elevated) end
Logs formatted standard error output at the warning level.
@param stderr [String] standard error output @api private
# File lib/kitchen/transport/winrm.rb, line 294 def log_stderr_on_warn(stderr) error_regexp = /<S S=\"Error\">/ if error_regexp.match(stderr) stderr .split(error_regexp)[1..-2] .map! { |line| line.sub(%r{_x000D__x000A_</S>}, "").rstrip } .each { |line| logger.warn(line) } else stderr .split("\r\n") .each { |line| logger.warn(line) } end end
Builds a `LoginCommand` for use by Linux-based platforms.
@return [LoginCommand] a login command @api private
# File lib/kitchen/transport/winrm.rb, line 313 def login_command_for_linux xfreerdp = Util.command_exists? "xfreerdp" unless xfreerdp raise WinrmFailed, "xfreerdp binary not found. Please install freerdp2-x11 on Debian-based systems or freerdp on Redhat-based systems." end args = %W{/u:#{options[:user]}} args += %W{/p:#{options[:password]}} if options.key?(:password) args += %W{/v:#{URI.parse(options[:endpoint]).host}:#{rdp_port}} args += %W{/cert-tofu} # always accept certificate LoginCommand.new(xfreerdp, args) end
Builds a `LoginCommand` for use by Mac-based platforms.
@return [LoginCommand] a login command @api private
# File lib/kitchen/transport/winrm.rb, line 331 def login_command_for_mac create_rdp_doc(mac: true) LoginCommand.new("open", rdp_doc_path) end
Builds a `LoginCommand` for use by Windows-based platforms.
@return [LoginCommand] a login command @api private
# File lib/kitchen/transport/winrm.rb, line 341 def login_command_for_windows create_rdp_doc LoginCommand.new("mstsc", rdp_doc_path) end
@return [String] path to the local RDP document @api private
# File lib/kitchen/transport/winrm.rb, line 349 def rdp_doc_path File.join(kitchen_root, ".kitchen", "#{instance_name}.rdp") end
String representation of object, reporting its connection details and configuration.
@api private
# File lib/kitchen/transport/winrm.rb, line 396 def to_s "<#{options.inspect}>" end
Establishes a remote shell session, or establishes one when invoked the first time.
@param retry_options [Hash] retry options for the initial connection @return [Winrm::Shells::Powershell] the command shell session @api private
# File lib/kitchen/transport/winrm.rb, line 359 def unelevated_session(retry_options = {}) @unelevated_session ||= connection(retry_options).shell(:powershell) end
# File lib/kitchen/transport/winrm.rb, line 266 def unelevated_temp_dir @unelevated_temp_dir ||= unelevated_session.run("$env:temp").stdout.chomp end