From f7cff6dbe27e9e98598f24df9f482599593ff121 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Fri, 1 Jul 2016 10:07:18 +0200 Subject: [PATCH] Allow requesting a PTY This change allows the user to request a pseudo-tty: ``` require 'train' t = Train.create('ssh', host: '127.0.0.1', port: '2200', user: 'vagrant', password: 'vagrant', sudo: true, pty: true); puts t.connection.run_command("ls /") ``` While this allows passing the `requiretty` sudoer defaults of certain RedHat-ish distributions, it comes at the cost of having stderr and stdout merged together. The change includes a warning for this. The command above, for example, outputs: ``` ``` For comparison, this is the output _without_ the PTY: ``` ``` Note how `ls` behaves differently -- in the presence of a PTY, it will prettify its output. --- lib/train/transports/ssh.rb | 6 ++++++ lib/train/transports/ssh_connection.rb | 6 ++++++ test/unit/transports/ssh_test.rb | 12 ++++++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/train/transports/ssh.rb b/lib/train/transports/ssh.rb index 05de9f8e..96debf98 100644 --- a/lib/train/transports/ssh.rb +++ b/lib/train/transports/ssh.rb @@ -27,6 +27,7 @@ module Train::Transports # # @author Fletcher Nichol class SSHFailed < Train::TransportError; end + class SSHPTYFailed < Train::TransportError; end # A Transport which uses the SSH protocol to execute commands and transfer # files. @@ -55,6 +56,7 @@ class SSH < Train.plugin(1) option :connection_retry_sleep, default: 1 option :max_wait_until_ready, default: 600 option :compression, default: false + option :pty, default: false option :compression_level do |opts| # on nil or false: set compression level to 0 @@ -98,6 +100,10 @@ def validate_options(options) options[:auth_methods].push('password', 'keyboard-interactive') end + if options[:pty] + logger.warn('[SSH] PTY requested: stderr will be merged into stdout') + end + super self end diff --git a/lib/train/transports/ssh_connection.rb b/lib/train/transports/ssh_connection.rb index 5e07194c..18e7f85b 100644 --- a/lib/train/transports/ssh_connection.rb +++ b/lib/train/transports/ssh_connection.rb @@ -79,6 +79,12 @@ def run_command(cmd) # wrap commands if that is configured cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil? + if @transport_options[:pty] + channel.request_pty do |_ch, success| + raise Train::Transports::SSHPTYFailed, "Requesting PTY failed" unless success + end + end + channel.exec(cmd) do |_, success| abort 'Couldn\'t execute command on SSH.' unless success diff --git a/test/unit/transports/ssh_test.rb b/test/unit/transports/ssh_test.rb index fb60ac19..f037ebf2 100644 --- a/test/unit/transports/ssh_test.rb +++ b/test/unit/transports/ssh_test.rb @@ -42,6 +42,10 @@ def detect_family it 'has default user' do ssh.options[:user].must_equal 'root' end + + it 'by default does not request a pty' do + ssh.options[:pty].must_equal false + end end describe 'opening a connection' do @@ -83,7 +87,7 @@ def detect_family end describe 'converting connection to string for logging' do - it "masks passwords" do + it 'masks passwords' do assert_output(/.*:password=>"".*/) do connection = cls.new(conf).connection puts "#{connection}" @@ -96,7 +100,7 @@ def detect_family cls.new(conf).connection end - it 'doesnt like host == nil' do + it 'does not like host == nil' do conf.delete(:host) proc { cls.new(conf).connection }.must_raise Train::ClientError end @@ -106,13 +110,13 @@ def detect_family cls.new(conf).connection.method(:options).call[:user] == 'root' end - it 'doesnt like key and password == nil' do + it 'does not like key and password == nil' do conf.delete(:password) conf.delete(:key_files) proc { cls.new(conf).connection }.must_raise Train::ClientError end - it 'wont connect if its not possible' do + it 'wont connect if it is not possible' do conf[:host] = 'localhost' conf[:port] = 1 conn = cls.new(conf).connection