2014-03-11 05:38:31 +00:00
|
|
|
require "timeout"
|
|
|
|
|
|
|
|
require "log4r"
|
|
|
|
|
|
|
|
require "vagrant/util/retryable"
|
2014-04-05 18:26:31 +00:00
|
|
|
require "vagrant/util/silence_warnings"
|
|
|
|
|
|
|
|
Vagrant::Util::SilenceWarnings.silence! do
|
|
|
|
require "winrm"
|
|
|
|
end
|
2014-03-11 05:38:31 +00:00
|
|
|
|
2015-06-05 07:11:06 +00:00
|
|
|
require "winrm-fs"
|
2014-04-22 21:42:13 +00:00
|
|
|
|
2014-03-11 05:38:31 +00:00
|
|
|
module VagrantPlugins
|
|
|
|
module CommunicatorWinRM
|
|
|
|
class WinRMShell
|
|
|
|
include Vagrant::Util::Retryable
|
|
|
|
|
|
|
|
# These are the exceptions that we retry because they represent
|
|
|
|
# errors that are generally fixed from a retry and don't
|
|
|
|
# necessarily represent immediate failure cases.
|
|
|
|
@@exceptions_to_retry_on = [
|
|
|
|
HTTPClient::KeepAliveDisconnected,
|
|
|
|
WinRM::WinRMHTTPTransportError,
|
2015-01-28 15:47:03 +00:00
|
|
|
WinRM::WinRMAuthorizationError,
|
2015-08-21 20:37:10 +00:00
|
|
|
WinRM::WinRMWSManFault,
|
2014-03-11 05:38:31 +00:00
|
|
|
Errno::EACCES,
|
|
|
|
Errno::EADDRINUSE,
|
|
|
|
Errno::ECONNREFUSED,
|
|
|
|
Errno::ECONNRESET,
|
|
|
|
Errno::ENETUNREACH,
|
|
|
|
Errno::EHOSTUNREACH,
|
|
|
|
Timeout::Error
|
|
|
|
]
|
|
|
|
|
|
|
|
attr_reader :logger
|
|
|
|
attr_reader :host
|
|
|
|
attr_reader :port
|
2014-07-25 21:00:10 +00:00
|
|
|
attr_reader :username
|
|
|
|
attr_reader :password
|
2015-08-30 17:09:25 +00:00
|
|
|
attr_reader :execution_time_limit
|
2014-07-22 23:30:29 +00:00
|
|
|
attr_reader :config
|
2014-03-11 05:38:31 +00:00
|
|
|
|
2014-07-22 23:30:29 +00:00
|
|
|
def initialize(host, port, config)
|
2014-03-11 05:38:31 +00:00
|
|
|
@logger = Log4r::Logger.new("vagrant::communication::winrmshell")
|
|
|
|
@logger.debug("initializing WinRMShell")
|
|
|
|
|
2014-07-22 22:28:50 +00:00
|
|
|
@host = host
|
2014-07-22 23:30:29 +00:00
|
|
|
@port = port
|
2014-07-25 21:00:10 +00:00
|
|
|
@username = config.username
|
|
|
|
@password = config.password
|
2015-08-30 17:09:25 +00:00
|
|
|
@execution_time_limit = config.execution_time_limit
|
2014-07-22 23:30:29 +00:00
|
|
|
@config = config
|
2014-03-11 05:38:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def powershell(command, &block)
|
2015-09-23 03:27:13 +00:00
|
|
|
# Suppress the progress stream from leaking to stderr
|
|
|
|
command = "$ProgressPreference='SilentlyContinue';\r\n" + command
|
2014-04-24 03:38:16 +00:00
|
|
|
command << "\r\n"
|
2015-09-23 03:27:13 +00:00
|
|
|
# Ensure an exit code
|
2014-08-24 17:09:14 +00:00
|
|
|
command << "if ($?) { exit 0 } else { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }"
|
2014-03-11 05:38:31 +00:00
|
|
|
execute_shell(command, :powershell, &block)
|
|
|
|
end
|
|
|
|
|
|
|
|
def cmd(command, &block)
|
|
|
|
execute_shell(command, :cmd, &block)
|
|
|
|
end
|
|
|
|
|
2014-04-22 18:29:22 +00:00
|
|
|
def wql(query, &block)
|
|
|
|
execute_shell(query, :wql, &block)
|
2014-03-11 05:38:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def upload(from, to)
|
2015-01-20 19:45:57 +00:00
|
|
|
file_manager = WinRM::FS::FileManager.new(session)
|
|
|
|
file_manager.upload(from, to)
|
2014-03-11 05:38:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def download(from, to)
|
2015-01-20 19:45:57 +00:00
|
|
|
file_manager = WinRM::FS::FileManager.new(session)
|
|
|
|
file_manager.download(from, to)
|
2014-03-11 05:38:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
def execute_shell(command, shell=:powershell, &block)
|
2014-05-22 16:35:12 +00:00
|
|
|
raise Errors::WinRMInvalidShell, shell: shell unless [:powershell, :cmd, :wql].include?(shell)
|
2014-03-11 05:38:31 +00:00
|
|
|
|
|
|
|
begin
|
|
|
|
execute_shell_with_retry(command, shell, &block)
|
|
|
|
rescue => e
|
|
|
|
raise_winrm_exception(e, shell, command)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def execute_shell_with_retry(command, shell, &block)
|
2015-01-28 18:40:22 +00:00
|
|
|
retryable(tries: @config.max_tries, on: @@exceptions_to_retry_on, sleep: @config.retry_delay) do
|
2014-03-11 05:38:31 +00:00
|
|
|
@logger.debug("#{shell} executing:\n#{command}")
|
|
|
|
output = session.send(shell, command) do |out, err|
|
|
|
|
block.call(:stdout, out) if block_given? && out
|
|
|
|
block.call(:stderr, err) if block_given? && err
|
|
|
|
end
|
2015-01-06 00:52:57 +00:00
|
|
|
|
2014-04-22 18:29:22 +00:00
|
|
|
@logger.debug("Output: #{output.inspect}")
|
2015-01-06 00:52:57 +00:00
|
|
|
|
|
|
|
# Verify that we didn't get a parser error, and if so we should
|
|
|
|
# set the exit code to 1. Parse errors return exit code 0 so we
|
|
|
|
# need to do this.
|
|
|
|
if output[:exitcode] == 0
|
|
|
|
(output[:data] || []).each do |data|
|
|
|
|
next if !data[:stderr]
|
|
|
|
if data[:stderr].include?("ParserError")
|
|
|
|
@logger.warn("Detected ParserError, setting exit code to 1")
|
|
|
|
output[:exitcode] = 1
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-03-11 05:38:31 +00:00
|
|
|
return output
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-12-09 00:09:11 +00:00
|
|
|
def raise_winrm_exception(exception, shell = nil, command = nil)
|
|
|
|
case exception
|
|
|
|
when WinRM::WinRMAuthorizationError
|
|
|
|
raise Errors::AuthenticationFailed,
|
|
|
|
user: @config.username,
|
|
|
|
password: @config.password,
|
|
|
|
endpoint: endpoint,
|
|
|
|
message: exception.message
|
|
|
|
when WinRM::WinRMHTTPTransportError
|
|
|
|
raise Errors::ExecutionError,
|
|
|
|
shell: shell,
|
|
|
|
command: command,
|
|
|
|
message: exception.message
|
|
|
|
when OpenSSL::SSL::SSLError
|
|
|
|
raise Errors::SSLError, message: exception.message
|
|
|
|
when HTTPClient::TimeoutError
|
|
|
|
raise Errors::ConnectionTimeout, message: exception.message
|
2015-07-15 17:41:10 +00:00
|
|
|
when Errno::ETIMEDOUT
|
|
|
|
raise Errors::ConnectionTimeout
|
|
|
|
# This is raised if the connection timed out
|
2014-12-09 00:09:11 +00:00
|
|
|
when Errno::ECONNREFUSED
|
|
|
|
# This is raised if we failed to connect the max amount of times
|
|
|
|
raise Errors::ConnectionRefused
|
|
|
|
when Errno::ECONNRESET
|
|
|
|
# This is raised if we failed to connect the max number of times
|
|
|
|
# due to an ECONNRESET.
|
|
|
|
raise Errors::ConnectionReset
|
|
|
|
when Errno::EHOSTDOWN
|
|
|
|
# This is raised if we get an ICMP DestinationUnknown error.
|
|
|
|
raise Errors::HostDown
|
|
|
|
when Errno::EHOSTUNREACH
|
|
|
|
# This is raised if we can't work out how to route traffic.
|
|
|
|
raise Errors::NoRoute
|
|
|
|
else
|
2014-12-10 20:15:07 +00:00
|
|
|
raise Errors::ExecutionError,
|
|
|
|
shell: shell,
|
|
|
|
command: command,
|
|
|
|
message: exception.message
|
2014-12-09 00:09:11 +00:00
|
|
|
end
|
2014-03-11 05:38:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def new_session
|
|
|
|
@logger.info("Attempting to connect to WinRM...")
|
|
|
|
@logger.info(" - Host: #{@host}")
|
|
|
|
@logger.info(" - Port: #{@port}")
|
2014-07-22 23:30:29 +00:00
|
|
|
@logger.info(" - Username: #{@config.username}")
|
|
|
|
@logger.info(" - Transport: #{@config.transport}")
|
2014-03-11 05:38:31 +00:00
|
|
|
|
2014-07-22 23:58:27 +00:00
|
|
|
client = ::WinRM::WinRMWebService.new(endpoint, @config.transport.to_sym, endpoint_options)
|
2014-07-22 23:30:29 +00:00
|
|
|
client.set_timeout(@config.timeout)
|
2014-03-11 05:38:31 +00:00
|
|
|
client.toggle_nori_type_casting(:off) #we don't want coersion of types
|
|
|
|
client
|
|
|
|
end
|
|
|
|
|
|
|
|
def session
|
|
|
|
@session ||= new_session
|
|
|
|
end
|
|
|
|
|
|
|
|
def endpoint
|
2014-07-23 18:56:49 +00:00
|
|
|
case @config.transport.to_sym
|
2014-07-22 23:58:27 +00:00
|
|
|
when :ssl
|
|
|
|
"https://#{@host}:#{@port}/wsman"
|
|
|
|
when :plaintext
|
|
|
|
"http://#{@host}:#{@port}/wsman"
|
|
|
|
else
|
|
|
|
raise Errors::WinRMInvalidTransport, transport: @config.transport
|
|
|
|
end
|
2014-03-11 05:38:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def endpoint_options
|
2014-07-25 21:00:10 +00:00
|
|
|
{ user: @username,
|
|
|
|
pass: @password,
|
2014-05-22 16:35:12 +00:00
|
|
|
host: @host,
|
|
|
|
port: @port,
|
2014-06-03 03:49:19 +00:00
|
|
|
basic_auth_only: true,
|
2014-07-22 23:30:29 +00:00
|
|
|
no_ssl_peer_verification: !@config.ssl_peer_verification }
|
2014-03-11 05:38:31 +00:00
|
|
|
end
|
|
|
|
end #WinShell class
|
|
|
|
end
|
|
|
|
end
|