Skip to content

Commit

Permalink
Allow requesting a PTY
Browse files Browse the repository at this point in the history
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:

```
<struct Train::Extras::CommandResult stdout="/etc/profile.d/lang.sh: line 19: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory\r\nbin dev  home  lib64\tmnt  proc  run\t shared  sys  usr      var\r\nboot  etc  lib\t media\topt  root  sbin  srv\t tmp  vagrant\r\n", stderr="", exit_status=0>
```

For comparison, this is the output _without_ the PTY:

```
<struct Train::Extras::CommandResult stdout="bin\nboot\ndev\netc\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nshared\nsrv\nsys\ntmp\nusr\nvagrant\nvar\n", stderr="/etc/profile.d/lang.sh: line 19: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory\n", exit_status=0>
```

Note how `ls` behaves differently -- in the presence of a PTY, it will
prettify its output.
  • Loading branch information
srenatus committed Jul 1, 2016
1 parent fcae897 commit f7cff6d
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 4 deletions.
6 changes: 6 additions & 0 deletions lib/train/transports/ssh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module Train::Transports
#
# @author Fletcher Nichol <[email protected]>
class SSHFailed < Train::TransportError; end
class SSHPTYFailed < Train::TransportError; end

# A Transport which uses the SSH protocol to execute commands and transfer
# files.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions lib/train/transports/ssh_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 8 additions & 4 deletions test/unit/transports/ssh_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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=>"<hidden>".*/) do
connection = cls.new(conf).connection
puts "#{connection}"
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit f7cff6d

Please sign in to comment.