diff --git a/lib/vagrant/action/builtin/ssh_run.rb b/lib/vagrant/action/builtin/ssh_run.rb index 9d6cb8ab7..9ea58ca50 100644 --- a/lib/vagrant/action/builtin/ssh_run.rb +++ b/lib/vagrant/action/builtin/ssh_run.rb @@ -44,7 +44,9 @@ module Vagrant # Allow the user to specify a tty or non-tty manually, but if they # don't then we default to a TTY - if !opts[:extra_args].include?("-t") && !opts[:extra_args].include?("-T") + if !opts[:extra_args].include?("-t") && + !opts[:extra_args].include?("-T") && + env[:tty] opts[:extra_args] << "-t" end diff --git a/plugins/commands/ssh/command.rb b/plugins/commands/ssh/command.rb index d136d33a1..1e153d2be 100644 --- a/plugins/commands/ssh/command.rb +++ b/plugins/commands/ssh/command.rb @@ -9,6 +9,7 @@ module VagrantPlugins def execute options = {} + options[:tty] = true opts = OptionParser.new do |o| o.banner = "Usage: vagrant ssh [options] [name|id] [-- extra ssh args]" @@ -23,6 +24,10 @@ module VagrantPlugins o.on("-p", "--plain", "Plain mode, leaves authentication up to user") do |p| options[:plain_mode] = p end + + o.on("-t", "--[no-]tty", "Enables tty when executing an ssh command (defaults to true)") do |t| + options[:tty] = t + end end # Parse out the extra args to send to SSH, which is everything @@ -48,7 +53,8 @@ module VagrantPlugins @logger.debug("Executing single command on remote machine: #{options[:command]}") env = vm.action(:ssh_run, ssh_opts: ssh_opts, - ssh_run_command: options[:command],) + ssh_run_command: options[:command], + tty: options[:tty],) # Exit with the exit status of the command or a 0 if we didn't # get one. diff --git a/test/unit/vagrant/action/builtin/ssh_run_test.rb b/test/unit/vagrant/action/builtin/ssh_run_test.rb new file mode 100644 index 000000000..56609607d --- /dev/null +++ b/test/unit/vagrant/action/builtin/ssh_run_test.rb @@ -0,0 +1,83 @@ +require File.expand_path("../../../../base", __FILE__) + +describe Vagrant::Action::Builtin::SSHRun do + let(:app) { lambda { |env| } } + let(:env) { { machine: machine, tty: true } } + + # SSH configuration information mock + let(:ssh) do + double("ssh", + timeout: 1, + host: nil, + port: 5986, + guest_port: 5986, + pty: false, + keep_alive: false, + insert_key: false, + shell: 'bash -l' + ) + end + + # Configuration mock + let(:config) { double("config", ssh: ssh) } + + let(:machine) do + double("machine", + config: config,) + end + + let(:machine_ssh_info) { {} } + let(:ssh_klass) { Vagrant::Util::SSH } + + before(:each) do + # Stub the methods so that even if we test incorrectly, no side + # effects actually happen. + allow(ssh_klass).to receive(:exec) + allow(machine).to receive(:ssh_info).and_return(machine_ssh_info) + end + + it "should raise an exception if SSH is not ready" do + not_ready_machine = double("machine") + allow(not_ready_machine).to receive(:ssh_info).and_return(nil) + + env[:machine] = not_ready_machine + expect { described_class.new(app, env).call(env) }. + to raise_error(Vagrant::Errors::SSHNotReady) + end + + it "should exec with the SSH info in the env if given" do + ssh_info = { foo: :bar } + opts = {:extra_args=>["-t", "bash -l -c 'echo test'"], :subprocess=>true} + + expect(ssh_klass).to receive(:exec). + with(ssh_info, opts) + + env[:ssh_info] = ssh_info + env[:ssh_run_command] = "echo test" + described_class.new(app, env).call(env) + end + + it "should exec with the SSH info in the env if given and disable tty" do + ssh_info = { foo: :bar } + opts = {:extra_args=>["bash -l -c 'echo test'"], :subprocess=>true} + env[:tty] = false + + expect(ssh_klass).to receive(:exec). + with(ssh_info, opts) + + env[:ssh_info] = ssh_info + env[:ssh_run_command] = "echo test" + described_class.new(app, env).call(env) + end + + it "should exec with the options given in `ssh_opts`" do + ssh_opts = { foo: :bar } + + expect(ssh_klass).to receive(:exec). + with(machine_ssh_info, ssh_opts) + + env[:ssh_opts] = ssh_opts + env[:ssh_run_command] = "echo test" + described_class.new(app, env).call(env) + end +end