communicators/ssh: cleanup PTY code for GH-4408]

This commit is contained in:
Mitchell Hashimoto 2014-08-29 09:41:35 -07:00
parent a9be7ce224
commit 138aa5aad3
2 changed files with 32 additions and 28 deletions

View File

@ -45,6 +45,8 @@ BUG FIXES:
- commands/rsync-auto: Destroyed machines won't raise exceptions. [GH-4031] - commands/rsync-auto: Destroyed machines won't raise exceptions. [GH-4031]
- communicators/ssh: Nicer error if remote unexpectedly disconects. [GH-4038] - communicators/ssh: Nicer error if remote unexpectedly disconects. [GH-4038]
- communicators/ssh: Clean error when max sessions is hit. [GH-4044] - communicators/ssh: Clean error when max sessions is hit. [GH-4044]
- communicators/ssh: Fix many issues around PTY-enabled output parsing.
[GH-4408]
- communicators/winrm: Support `mkdir` [GH-4271] - communicators/winrm: Support `mkdir` [GH-4271]
- communicators/winrm: Properly escape double quotes. [GH-4309] - communicators/winrm: Properly escape double quotes. [GH-4309]
- guests/centos: Fix issues when NFS client is installed by restarting - guests/centos: Fix issues when NFS client is installed by restarting

View File

@ -19,6 +19,9 @@ module VagrantPlugins
module CommunicatorSSH module CommunicatorSSH
# This class provides communication with the VM via SSH. # This class provides communication with the VM via SSH.
class Communicator < Vagrant.plugin("2", :communicator) class Communicator < Vagrant.plugin("2", :communicator)
PTY_DELIM_START = "bccbb768c119429488cfd109aacea6b5-pty"
PTY_DELIM_END = "bccbb768c119429488cfd109aacea6b5-pty"
include Vagrant::Util::ANSIEscapeCodeRemover include Vagrant::Util::ANSIEscapeCodeRemover
include Vagrant::Util::Retryable include Vagrant::Util::Retryable
@ -401,9 +404,6 @@ module VagrantPlugins
return yield connection if block_given? return yield connection if block_given?
end end
DELIM_START = 'UniqueStartStringPTY'
DELIM_END = 'UniqueEndStringPTY'
# Executes the command on an SSH connection within a login shell. # Executes the command on an SSH connection within a login shell.
def shell_execute(connection, command, **opts) def shell_execute(connection, command, **opts)
opts = { opts = {
@ -424,14 +424,16 @@ module VagrantPlugins
shell_cmd = shell if shell shell_cmd = shell if shell
shell_cmd = "sudo -E -H #{shell_cmd}" if sudo shell_cmd = "sudo -E -H #{shell_cmd}" if sudo
use_tty = false # These variables are used to scrub PTY output if we're in a PTY
stdout = '' pty = false
pty_stdout = ""
# Open the channel so we can execute or command # Open the channel so we can execute or command
channel = connection.open_channel do |ch| channel = connection.open_channel do |ch|
if @machine.config.ssh.pty if @machine.config.ssh.pty
ch.request_pty do |ch2, success| ch.request_pty do |ch2, success|
use_tty = success and command != '' pty = success && command != ""
if success if success
@logger.debug("pty obtained for connection") @logger.debug("pty obtained for connection")
else else
@ -446,8 +448,8 @@ module VagrantPlugins
# Filter out the clear screen command # Filter out the clear screen command
data = remove_ansi_escape_codes(data) data = remove_ansi_escape_codes(data)
@logger.debug("stdout: #{data}") @logger.debug("stdout: #{data}")
if use_tty if pty
stdout << data pty_stdout << data
else else
yield :stdout, data if block_given? yield :stdout, data if block_given?
end end
@ -497,21 +499,21 @@ module VagrantPlugins
end end
end end
# Output the command # Output the command. If we're using a pty we have to do
if use_tty # a little dance to make sure we get all the output properly
ch2.send_data "stty raw -echo\n" # without the cruft added from pty mode.
ch2.send_data "export PS1=\n" if pty
ch2.send_data "export PS2=\n" data = "stty raw -echo\n"
ch2.send_data "export PROMPT_COMMAND=\n" data += "export PS1=\n"
sleep(0.1) data += "export PS2=\n"
data = "printf #{DELIM_START}\n" data += "export PROMPT_COMMAND=\n"
data += "printf #{PTY_DELIM_START}\n"
data += "#{command}\n" data += "#{command}\n"
data += "exitcode=$?\n" data += "exitcode=$?\n"
data += "printf #{DELIM_END}\n" data += "printf #{PTY_DELIM_END}\n"
data += "exit $exitcode\n"
data = data.force_encoding('ASCII-8BIT') data = data.force_encoding('ASCII-8BIT')
ch2.send_data data ch2.send_data data
# Remember to exit or this channel will hang open
ch2.send_data "exit $exitcode\n"
else else
ch2.send_data "#{command}\n".force_encoding('ASCII-8BIT') ch2.send_data "#{command}\n".force_encoding('ASCII-8BIT')
# Remember to exit or this channel will hang open # Remember to exit or this channel will hang open
@ -520,9 +522,7 @@ module VagrantPlugins
# Send eof to let server know we're done # Send eof to let server know we're done
ch2.eof! ch2.eof!
ch2.wait
end end
ch.wait
end end
begin begin
@ -548,7 +548,7 @@ module VagrantPlugins
@logger.info( @logger.info(
"SSH connection unexpected closed. Assuming reboot or something.") "SSH connection unexpected closed. Assuming reboot or something.")
exit_status = 0 exit_status = 0
use_tty = false pty = false
rescue Net::SSH::ChannelOpenFailed rescue Net::SSH::ChannelOpenFailed
raise Vagrant::Errors::SSHChannelOpenFail raise Vagrant::Errors::SSHChannelOpenFail
rescue Net::SSH::Disconnect rescue Net::SSH::Disconnect
@ -559,14 +559,16 @@ module VagrantPlugins
keep_alive.kill if keep_alive keep_alive.kill if keep_alive
end end
if use_tty # If we're in a PTY, we now finally parse the output
@logger.debug("stdout: #{stdout}") if pty
if not stdout.include? DELIM_START or not stdout.include? DELIM_END @logger.debug("PTY stdout: #{pty_stdout}")
@logger.error("Error parsing TTY output") if !pty_stdout.include?(PTY_DELIM_START) || !pty_stdout.include?(PTY_DELIM_END)
@logger.error("PTY stdout doesn't include delims")
raise Vagrant::Errors::SSHInvalidShell.new raise Vagrant::Errors::SSHInvalidShell.new
end end
data = stdout[/.*#{DELIM_START}(.*?)#{DELIM_END}/m, 1]
@logger.debug("selected stdout: #{data}") data = stdout[/.*#{PTY_DELIM_START}(.*?)#{PTY_DELIM_END}/m, 1]
@logger.debug("PTY stdout parsed: #{data}")
yield :stdout, data if block_given? yield :stdout, data if block_given?
end end