Merge pull request #8485 from chrisroberts/communicators/win-ssh
Add winssh communicator
This commit is contained in:
commit
232a44f46d
|
@ -143,7 +143,7 @@ module VagrantPlugins
|
|||
# If we're already attempting to switch out the SSH key, then
|
||||
# just return that we're ready (for Machine#guest).
|
||||
@lock.synchronize do
|
||||
return true if @inserted_key || !@machine.config.ssh.insert_key
|
||||
return true if @inserted_key || !machine_config_ssh.insert_key
|
||||
@inserted_key = true
|
||||
end
|
||||
|
||||
|
@ -458,9 +458,9 @@ module VagrantPlugins
|
|||
# Determine the shell to execute. Prefer the explicitly passed in shell
|
||||
# over the default configured shell. If we are using `sudo` then we
|
||||
# need to wrap the shell in a `sudo` call.
|
||||
cmd = @machine.config.ssh.shell
|
||||
cmd = machine_config_ssh.shell
|
||||
cmd = shell if shell
|
||||
cmd = @machine.config.ssh.sudo_command.gsub("%c", cmd) if sudo
|
||||
cmd = machine_config_ssh.sudo_command.gsub("%c", cmd) if sudo
|
||||
cmd
|
||||
end
|
||||
|
||||
|
@ -482,7 +482,7 @@ module VagrantPlugins
|
|||
|
||||
# Open the channel so we can execute or command
|
||||
channel = connection.open_channel do |ch|
|
||||
if @machine.config.ssh.pty
|
||||
if machine_config_ssh.pty
|
||||
ch.request_pty do |ch2, success|
|
||||
pty = success && command != ""
|
||||
|
||||
|
@ -611,7 +611,7 @@ module VagrantPlugins
|
|||
begin
|
||||
keep_alive = nil
|
||||
|
||||
if @machine.config.ssh.keep_alive
|
||||
if machine_config_ssh.keep_alive
|
||||
# Begin sending keep-alive packets while we wait for the script
|
||||
# to complete. This avoids connections closing on long-running
|
||||
# scripts.
|
||||
|
@ -687,9 +687,13 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def generate_environment_export(env_key, env_value)
|
||||
template = @machine.config.ssh.export_command_template
|
||||
template = machine_config_ssh.export_command_template
|
||||
template.sub("%ENV_KEY%", env_key).sub("%ENV_VALUE%", env_value) + "\n"
|
||||
end
|
||||
|
||||
def machine_config_ssh
|
||||
@machine.config.ssh
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
require File.expand_path("../../ssh/communicator", __FILE__)
|
||||
|
||||
module VagrantPlugins
|
||||
module CommunicatorWinSSH
|
||||
# This class provides communication with a Windows VM running
|
||||
# the Windows native port of OpenSSH
|
||||
class Communicator < VagrantPlugins::CommunicatorSSH::Communicator
|
||||
|
||||
def initialize(machine)
|
||||
super
|
||||
@logger = Log4r::Logger.new("vagrant::communication::winssh")
|
||||
end
|
||||
|
||||
# Executes the command on an SSH connection within a login shell.
|
||||
def shell_execute(connection, command, **opts)
|
||||
opts = {
|
||||
sudo: false,
|
||||
shell: nil
|
||||
}.merge(opts)
|
||||
|
||||
sudo = opts[:sudo]
|
||||
|
||||
@logger.info("Execute: #{command} (sudo=#{sudo.inspect})")
|
||||
exit_status = nil
|
||||
|
||||
# Open the channel so we can execute or command
|
||||
channel = connection.open_channel do |ch|
|
||||
marker_found = false
|
||||
data_buffer = ''
|
||||
stderr_marker_found = false
|
||||
stderr_data_buffer = ''
|
||||
|
||||
tfile = Tempfile.new('vagrant-ssh')
|
||||
remote_ext = machine_config_ssh.shell.to_s == "powershell" ? "ps1" : "bat"
|
||||
remote_name = "C:\\Windows\\Temp\\#{File.basename(tfile.path)}.#{remote_ext}"
|
||||
|
||||
if machine_config_ssh.shell.to_s == "powershell"
|
||||
base_cmd = "powershell -File #{remote_name}"
|
||||
tfile.puts <<-SCRIPT.force_encoding('ASCII-8BIT')
|
||||
Remove-Item #{remote_name}
|
||||
Write-Host #{CMD_GARBAGE_MARKER}
|
||||
[Console]::Error.WriteLine("#{CMD_GARBAGE_MARKER}")
|
||||
#{command}
|
||||
SCRIPT
|
||||
else
|
||||
base_cmd = remote_name
|
||||
tfile.puts <<-SCRIPT.force_encoding('ASCII-8BIT')
|
||||
ECHO OFF
|
||||
ECHO #{CMD_GARBAGE_MARKER}
|
||||
ECHO #{CMD_GARBAGE_MARKER} 1>&2
|
||||
#{command}
|
||||
SCRIPT
|
||||
end
|
||||
|
||||
tfile.close
|
||||
upload(tfile.path, remote_name)
|
||||
tfile.delete
|
||||
|
||||
base_cmd = shell_cmd(opts.merge(shell: base_cmd))
|
||||
@logger.debug("Base SSH exec command: #{base_cmd}")
|
||||
|
||||
ch.exec(base_cmd) do |ch2, _|
|
||||
# Setup the channel callbacks so we can get data and exit status
|
||||
ch2.on_data do |ch3, data|
|
||||
# Filter out the clear screen command
|
||||
data = remove_ansi_escape_codes(data)
|
||||
|
||||
if !marker_found
|
||||
data_buffer << data
|
||||
marker_index = data_buffer.index(CMD_GARBAGE_MARKER)
|
||||
if marker_index
|
||||
marker_found = true
|
||||
data_buffer.slice!(0, marker_index + CMD_GARBAGE_MARKER.size)
|
||||
data.replace(data_buffer)
|
||||
data_buffer = nil
|
||||
end
|
||||
end
|
||||
|
||||
if block_given? && marker_found
|
||||
yield :stdout, data
|
||||
end
|
||||
end
|
||||
|
||||
ch2.on_extended_data do |ch3, type, data|
|
||||
# Filter out the clear screen command
|
||||
data = remove_ansi_escape_codes(data)
|
||||
@logger.debug("stderr: #{data}")
|
||||
if !stderr_marker_found
|
||||
stderr_data_buffer << data
|
||||
marker_index = stderr_data_buffer.index(CMD_GARBAGE_MARKER)
|
||||
if marker_index
|
||||
marker_found = true
|
||||
stderr_data_buffer.slice!(0, marker_index + CMD_GARBAGE_MARKER.size)
|
||||
data.replace(stderr_data_buffer.lstrip)
|
||||
data_buffer = nil
|
||||
end
|
||||
end
|
||||
|
||||
if block_given? && marker_found
|
||||
yield :stderr, data
|
||||
end
|
||||
end
|
||||
|
||||
ch2.on_request("exit-status") do |ch3, data|
|
||||
exit_status = data.read_long
|
||||
@logger.debug("Exit status: #{exit_status}")
|
||||
|
||||
# Close the channel, since after the exit status we're
|
||||
# probably done. This fixes up issues with hanging.
|
||||
ch.close
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
keep_alive = nil
|
||||
|
||||
if @machine.config.ssh.keep_alive
|
||||
# Begin sending keep-alive packets while we wait for the script
|
||||
# to complete. This avoids connections closing on long-running
|
||||
# scripts.
|
||||
keep_alive = Thread.new do
|
||||
loop do
|
||||
sleep 5
|
||||
@logger.debug("Sending SSH keep-alive...")
|
||||
connection.send_global_request("keep-alive@openssh.com")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Wait for the channel to complete
|
||||
begin
|
||||
channel.wait
|
||||
rescue Errno::ECONNRESET, IOError
|
||||
@logger.info(
|
||||
"SSH connection unexpected closed. Assuming reboot or something.")
|
||||
exit_status = 0
|
||||
pty = false
|
||||
rescue Net::SSH::ChannelOpenFailed
|
||||
raise Vagrant::Errors::SSHChannelOpenFail
|
||||
rescue Net::SSH::Disconnect
|
||||
raise Vagrant::Errors::SSHDisconnected
|
||||
end
|
||||
ensure
|
||||
# Kill the keep-alive thread
|
||||
keep_alive.kill if keep_alive
|
||||
end
|
||||
|
||||
# Return the final exit status
|
||||
return exit_status
|
||||
end
|
||||
|
||||
def machine_config_ssh
|
||||
@machine.config.winssh
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
require File.expand_path("../../../kernel_v2/config/ssh", __FILE__)
|
||||
|
||||
module VagrantPlugins
|
||||
module CommunicatorWinSSH
|
||||
class Config < VagrantPlugins::Kernel_V2::SSHConfig
|
||||
|
||||
def finalize!
|
||||
@shell = "cmd" if @shell == UNSET_VALUE
|
||||
@sudo_command = "%c" if @sudo_command == UNSET_VALUE
|
||||
if @export_command_template == UNSET_VALUE
|
||||
if @shell == "cmd"
|
||||
@export_command_template = 'set %ENV_KEY%="%ENV_VALUE%"'
|
||||
else
|
||||
@export_command_template = '$env:%ENV_KEY%="%ENV_VALUE%"'
|
||||
end
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def to_s
|
||||
"WINSSH"
|
||||
end
|
||||
|
||||
# Remove configuration options from regular SSH that are
|
||||
# not used within this communicator
|
||||
undef :forward_x11
|
||||
undef :pty
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
require "vagrant"
|
||||
|
||||
module VagrantPlugins
|
||||
module CommunicatorWinSSH
|
||||
class Plugin < Vagrant.plugin("2")
|
||||
name "windows ssh communicator"
|
||||
description <<-DESC
|
||||
DESC
|
||||
|
||||
communicator("winssh") do
|
||||
require File.expand_path("../communicator", __FILE__)
|
||||
Communicator
|
||||
end
|
||||
|
||||
config("winssh") do
|
||||
require_relative "config"
|
||||
Config
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,8 +18,11 @@ module VagrantPlugins
|
|||
args = " #{args.join(" ")}"
|
||||
end
|
||||
|
||||
if @machine.config.vm.communicator == :winrm
|
||||
case @machine.config.vm.communicator
|
||||
when :winrm
|
||||
provision_winrm(args)
|
||||
when :winssh
|
||||
provision_winssh(args)
|
||||
else
|
||||
provision_ssh(args)
|
||||
end
|
||||
|
@ -94,6 +97,59 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
# This is the provision method called if Windows OpenSSH is what is running
|
||||
# on the remote end, which assumes a non-POSIX-style host.
|
||||
def provision_winssh(args)
|
||||
with_script_file do |path|
|
||||
# Upload the script to the machine
|
||||
@machine.communicate.tap do |comm|
|
||||
env = config.env.map{|k,v| comm.generate_environment_export(k, v)}.join
|
||||
remote_ext = @machine.config.winssh.shell == "powershell" ? "ps1" : "bat"
|
||||
upload_path = "C:\\Windows\\Temp\\#{File.basename(path)}.#{remote_ext}"
|
||||
if remote_ext == "ps1"
|
||||
# Copy powershell_args from configuration
|
||||
shell_args = config.powershell_args
|
||||
# For PowerShell scripts bypass the execution policy unless already specified
|
||||
shell_args += " -ExecutionPolicy Bypass" if config.powershell_args !~ /[-\/]ExecutionPolicy/i
|
||||
# CLIXML output is kinda useless, especially on non-windows hosts
|
||||
shell_args += " -OutputFormat Text" if config.powershell_args !~ /[-\/]OutputFormat/i
|
||||
command = "#{env}\npowershell #{shell_args} #{upload_path}#{args}"
|
||||
else
|
||||
command = "#{env}\n#{upload_path}#{args}"
|
||||
end
|
||||
|
||||
# Reset upload path permissions for the current ssh user
|
||||
info = nil
|
||||
retryable(on: Vagrant::Errors::SSHNotReady, tries: 3, sleep: 2) do
|
||||
info = @machine.ssh_info
|
||||
raise Vagrant::Errors::SSHNotReady if info.nil?
|
||||
end
|
||||
|
||||
comm.upload(path.to_s, upload_path)
|
||||
|
||||
if config.name
|
||||
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
||||
script: "script: #{config.name}"))
|
||||
elsif config.path
|
||||
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
||||
script: path.to_s))
|
||||
else
|
||||
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
||||
script: "inline script"))
|
||||
end
|
||||
|
||||
# Execute it with sudo
|
||||
comm.execute(
|
||||
command,
|
||||
sudo: config.privileged,
|
||||
error_key: :ssh_bad_exit_status_muted
|
||||
) do |type, data|
|
||||
handle_comm(type, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This provisions using WinRM, which assumes a PowerShell
|
||||
# console on the other side.
|
||||
def provision_winrm(args)
|
||||
|
|
|
@ -0,0 +1,525 @@
|
|||
require File.expand_path("../../../../base", __FILE__)
|
||||
|
||||
require Vagrant.source_root.join("plugins/communicators/winssh/communicator")
|
||||
|
||||
describe VagrantPlugins::CommunicatorWinSSH::Communicator do
|
||||
include_context "unit"
|
||||
|
||||
let(:export_command_template){ 'export %ENV_KEY%="%ENV_VALUE%"' }
|
||||
|
||||
let(:ssh) do
|
||||
double("ssh",
|
||||
timeout: 1,
|
||||
host: nil,
|
||||
port: 5986,
|
||||
guest_port: 5986,
|
||||
keep_alive: false
|
||||
)
|
||||
end
|
||||
|
||||
# SSH configuration information mock
|
||||
let(:winssh) do
|
||||
double("winssh",
|
||||
insert_key: false,
|
||||
export_command_template: export_command_template,
|
||||
shell: 'cmd'
|
||||
)
|
||||
end
|
||||
# Configuration mock
|
||||
let(:config) { double("config", winssh: winssh, ssh: ssh) }
|
||||
# Provider mock
|
||||
let(:provider) { double("provider") }
|
||||
# UI mock
|
||||
let(:ui) { double("ui") }
|
||||
# Machine mock built with previously defined
|
||||
let(:machine) do
|
||||
double("machine",
|
||||
config: config,
|
||||
provider: provider,
|
||||
ui: ui
|
||||
)
|
||||
end
|
||||
# Subject instance to test
|
||||
let(:communicator){ @communicator ||= described_class.new(machine) }
|
||||
# Underlying net-ssh connection mock
|
||||
let(:connection) { double("connection") }
|
||||
# Base net-ssh connection channel mock
|
||||
let(:channel) { double("channel") }
|
||||
# net-ssh connection channel mock for running commands
|
||||
let(:command_channel) { double("command_channel") }
|
||||
# Default exit data for commands run
|
||||
let(:exit_data) { double("exit_data", read_long: 0) }
|
||||
# Marker used for flagging start of output
|
||||
let(:command_garbage_marker) { communicator.class.const_get(:CMD_GARBAGE_MARKER) }
|
||||
# Start marker output when PTY is enabled
|
||||
let(:pty_delim_start) { communicator.class.const_get(:PTY_DELIM_START) }
|
||||
# End marker output when PTY is enabled
|
||||
let(:pty_delim_end) { communicator.class.const_get(:PTY_DELIM_END) }
|
||||
# Command output returned on stdout
|
||||
let(:command_stdout_data) { '' }
|
||||
# Command output returned on stderr
|
||||
let(:command_stderr_data) { '' }
|
||||
# Mock for net-ssh scp
|
||||
let(:scp) { double("scp") }
|
||||
# Stub file to match commands
|
||||
let(:ssh_cmd_file){ double("ssh_cmd_file", path: "/dev/null/path") }
|
||||
|
||||
# Setup for commands using the net-ssh connection. This can be reused where needed
|
||||
# by providing to `before`
|
||||
connection_setup = proc do
|
||||
allow(connection).to receive(:logger)
|
||||
allow(connection).to receive(:closed?).and_return false
|
||||
allow(connection).to receive(:open_channel).
|
||||
and_yield(channel).and_return(channel)
|
||||
allow(channel).to receive(:wait).and_return true
|
||||
allow(channel).to receive(:close)
|
||||
allow(command_channel).to receive(:send_data)
|
||||
allow(command_channel).to receive(:eof!)
|
||||
allow(command_channel).to receive(:on_data).
|
||||
and_yield(nil, command_stdout_data)
|
||||
allow(command_channel).to receive(:on_extended_data).
|
||||
and_yield(nil, nil, command_stderr_data)
|
||||
allow(machine).to receive(:ssh_info).and_return(host: '10.1.2.3', port: 22)
|
||||
allow(channel).to receive(:[]=).with(any_args).and_return(true)
|
||||
allow(channel).to receive(:on_close)
|
||||
allow(channel).to receive(:on_data)
|
||||
allow(channel).to receive(:on_extended_data)
|
||||
allow(channel).to receive(:on_request)
|
||||
allow(channel).to receive(:on_process)
|
||||
allow(channel).to receive(:exec).with(anything).
|
||||
and_yield(command_channel, '').and_return channel
|
||||
expect(command_channel).to receive(:on_request).with('exit-status').
|
||||
and_yield(nil, exit_data)
|
||||
# Return mocked net-ssh connection during setup
|
||||
allow(communicator).to receive(:retryable).and_return(connection)
|
||||
allow(Tempfile).to receive(:new).with(/vagrant-ssh/).and_return(ssh_cmd_file)
|
||||
allow(ssh_cmd_file).to receive(:puts)
|
||||
allow(ssh_cmd_file).to receive(:close)
|
||||
allow(ssh_cmd_file).to receive(:delete)
|
||||
allow(scp).to receive(:upload!)
|
||||
allow(communicator).to receive(:scp_connect).and_return(true)
|
||||
end
|
||||
|
||||
describe ".wait_for_ready" do
|
||||
before(&connection_setup)
|
||||
context "with no static config (default scenario)" do
|
||||
before do
|
||||
allow(ui).to receive(:detail)
|
||||
end
|
||||
|
||||
context "when ssh_info requires a multiple tries before it is ready" do
|
||||
before do
|
||||
expect(machine).to receive(:ssh_info).
|
||||
and_return(nil).ordered
|
||||
expect(machine).to receive(:ssh_info).
|
||||
and_return(host: '10.1.2.3', port: 22).ordered
|
||||
end
|
||||
|
||||
it "retries ssh_info until ready" do
|
||||
# retries are every 0.5 so buffer the timeout just a hair over
|
||||
expect(communicator.wait_for_ready(0.6)).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".ready?" do
|
||||
before(&connection_setup)
|
||||
it "returns true if shell test is successful" do
|
||||
expect(communicator.ready?).to be_true
|
||||
end
|
||||
|
||||
context "with an invalid shell test" do
|
||||
before do
|
||||
expect(exit_data).to receive(:read_long).and_return 1
|
||||
end
|
||||
|
||||
it "returns raises SSHInvalidShell error" do
|
||||
expect{ communicator.ready? }.to raise_error Vagrant::Errors::SSHInvalidShell
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".execute" do
|
||||
before(&connection_setup)
|
||||
it "runs valid command and returns successful status code" do
|
||||
expect(ssh_cmd_file).to receive(:puts).with(/dir/)
|
||||
expect(communicator.execute("dir")).to eq(0)
|
||||
end
|
||||
|
||||
it "prepends UUID output to command for garbage removal" do
|
||||
expect(ssh_cmd_file).to receive(:puts).
|
||||
with(/ECHO OFF\nECHO #{command_garbage_marker}\nECHO #{command_garbage_marker}.*/)
|
||||
expect(communicator.execute("dir")).to eq(0)
|
||||
end
|
||||
|
||||
context "with command returning an error" do
|
||||
let(:exit_data) { double("exit_data", read_long: 1) }
|
||||
|
||||
it "raises error when exit-code is non-zero" do
|
||||
expect(ssh_cmd_file).to receive(:puts).with(/dir/)
|
||||
expect{ communicator.execute("dir") }.to raise_error(Vagrant::Errors::VagrantError)
|
||||
end
|
||||
|
||||
it "returns exit-code when exit-code is non-zero and error check is disabled" do
|
||||
expect(ssh_cmd_file).to receive(:puts).with(/dir/)
|
||||
expect(communicator.execute("dir", error_check: false)).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "with garbage content prepended to command output" do
|
||||
let(:command_stdout_data) do
|
||||
"Line of garbage\nMore garbage\n#{command_garbage_marker}Dir1\nDir2\n"
|
||||
end
|
||||
|
||||
it "removes any garbage output prepended to command output" do
|
||||
stdout = ''
|
||||
expect(
|
||||
communicator.execute("dir") do |type, data|
|
||||
if type == :stdout
|
||||
stdout << data
|
||||
end
|
||||
end
|
||||
).to eq(0)
|
||||
expect(stdout).to eq("Dir1\nDir2\n")
|
||||
end
|
||||
end
|
||||
|
||||
context "with garbage content prepended to command stderr output" do
|
||||
let(:command_stderr_data) do
|
||||
"Line of garbage\nMore garbage\n#{command_garbage_marker}Dir1\nDir2\n"
|
||||
end
|
||||
|
||||
it "removes any garbage output prepended to command stderr output" do
|
||||
stderr = ''
|
||||
expect(
|
||||
communicator.execute("dir") do |type, data|
|
||||
if type == :stderr
|
||||
stderr << data
|
||||
end
|
||||
end
|
||||
).to eq(0)
|
||||
expect(stderr).to eq("Dir1\nDir2\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".test" do
|
||||
before(&connection_setup)
|
||||
context "with exit code as zero" do
|
||||
it "returns true" do
|
||||
expect(communicator.test("dir")).to be_true
|
||||
end
|
||||
end
|
||||
|
||||
context "with exit code as non-zero" do
|
||||
before do
|
||||
expect(exit_data).to receive(:read_long).and_return 1
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
expect(communicator.test("false.exe")).to be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".upload" do
|
||||
before do
|
||||
expect(communicator).to receive(:scp_connect).and_yield(scp)
|
||||
end
|
||||
|
||||
it "uploads a directory if local path is a directory" do
|
||||
Dir.mktmpdir('vagrant-test') do |dir|
|
||||
expect(scp).to receive(:upload!).with(dir, 'C:\destination', recursive: true)
|
||||
communicator.upload(dir, 'C:\destination')
|
||||
end
|
||||
end
|
||||
|
||||
it "uploads a file if local path is a file" do
|
||||
file = Tempfile.new('vagrant-test')
|
||||
begin
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:\destination\file')
|
||||
communicator.upload(file.path, 'C:\destination\file')
|
||||
ensure
|
||||
file.delete
|
||||
end
|
||||
end
|
||||
|
||||
it "raises custom error on permission errors" do
|
||||
file = Tempfile.new('vagrant-test')
|
||||
begin
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:\destination\file').
|
||||
and_raise("Permission denied")
|
||||
expect{ communicator.upload(file.path, 'C:\destination\file') }.to(
|
||||
raise_error(Vagrant::Errors::SCPPermissionDenied)
|
||||
)
|
||||
ensure
|
||||
file.delete
|
||||
end
|
||||
end
|
||||
|
||||
it "does not raise custom error on non-permission errors" do
|
||||
file = Tempfile.new('vagrant-test')
|
||||
begin
|
||||
expect(scp).to receive(:upload!).with(instance_of(File), 'C:\destination\file').
|
||||
and_raise("Some other error")
|
||||
expect{ communicator.upload(file.path, 'C:\destination\file') }.to raise_error(RuntimeError)
|
||||
ensure
|
||||
file.delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".download" do
|
||||
before do
|
||||
expect(communicator).to receive(:scp_connect).and_yield(scp)
|
||||
end
|
||||
|
||||
it "calls scp to download file" do
|
||||
expect(scp).to receive(:download!).with('/path/from', 'C:\path\to')
|
||||
communicator.download('/path/from', 'C:\path\to')
|
||||
end
|
||||
end
|
||||
|
||||
describe ".connect" do
|
||||
|
||||
it "cannot be called directly" do
|
||||
expect{ communicator.connect }.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
context "with default configuration" do
|
||||
|
||||
before do
|
||||
expect(machine).to receive(:ssh_info).and_return(
|
||||
host: nil,
|
||||
port: nil,
|
||||
private_key_path: nil,
|
||||
username: nil,
|
||||
password: nil,
|
||||
keys_only: true,
|
||||
paranoid: false
|
||||
)
|
||||
end
|
||||
|
||||
it "has keys_only enabled" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
nil, nil, hash_including(
|
||||
keys_only: true
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "has paranoid disabled" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
nil, nil, hash_including(
|
||||
paranoid: false
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "does not include any private key paths" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
nil, nil, hash_excluding(
|
||||
keys: anything
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "includes `none` and `hostbased` auth methods" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
nil, nil, hash_including(
|
||||
auth_methods: ["none", "hostbased"]
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
end
|
||||
|
||||
context "with keys_only disabled and paranoid enabled" do
|
||||
|
||||
before do
|
||||
expect(machine).to receive(:ssh_info).and_return(
|
||||
host: nil,
|
||||
port: nil,
|
||||
private_key_path: nil,
|
||||
username: nil,
|
||||
password: nil,
|
||||
keys_only: false,
|
||||
paranoid: true
|
||||
)
|
||||
end
|
||||
|
||||
it "has keys_only enabled" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
nil, nil, hash_including(
|
||||
keys_only: false
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "has paranoid disabled" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
nil, nil, hash_including(
|
||||
paranoid: true
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
end
|
||||
|
||||
context "with host and port configured" do
|
||||
|
||||
before do
|
||||
expect(machine).to receive(:ssh_info).and_return(
|
||||
host: '127.0.0.1',
|
||||
port: 2222,
|
||||
private_key_path: nil,
|
||||
username: nil,
|
||||
password: nil,
|
||||
keys_only: true,
|
||||
paranoid: false
|
||||
)
|
||||
end
|
||||
|
||||
it "specifies configured host" do
|
||||
expect(Net::SSH).to receive(:start).with("127.0.0.1", anything, anything)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "has port defined" do
|
||||
expect(Net::SSH).to receive(:start).with("127.0.0.1", anything, hash_including(port: 2222))
|
||||
communicator.send(:connect)
|
||||
end
|
||||
end
|
||||
|
||||
context "with private_key_path configured" do
|
||||
before do
|
||||
expect(machine).to receive(:ssh_info).and_return(
|
||||
host: '127.0.0.1',
|
||||
port: 2222,
|
||||
private_key_path: ['/priv/key/path'],
|
||||
username: nil,
|
||||
password: nil,
|
||||
keys_only: true,
|
||||
paranoid: false
|
||||
)
|
||||
end
|
||||
|
||||
it "includes private key paths" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
anything, anything, hash_including(
|
||||
keys: ["/priv/key/path"]
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "includes `publickey` auth method" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
anything, anything, hash_including(
|
||||
auth_methods: ["none", "hostbased", "publickey"]
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
end
|
||||
|
||||
context "with username and password configured" do
|
||||
|
||||
before do
|
||||
expect(machine).to receive(:ssh_info).and_return(
|
||||
host: '127.0.0.1',
|
||||
port: 2222,
|
||||
private_key_path: nil,
|
||||
username: 'vagrant',
|
||||
password: 'vagrant',
|
||||
keys_only: true,
|
||||
paranoid: false
|
||||
)
|
||||
end
|
||||
|
||||
it "has username defined" do
|
||||
expect(Net::SSH).to receive(:start).with(anything, 'vagrant', anything).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "has password defined" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
anything, anything, hash_including(
|
||||
password: 'vagrant'
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "includes `password` auth method" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
anything, anything, hash_including(
|
||||
auth_methods: ["none", "hostbased", "password"]
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
end
|
||||
|
||||
context "with password and private_key_path configured" do
|
||||
|
||||
before do
|
||||
expect(machine).to receive(:ssh_info).and_return(
|
||||
host: '127.0.0.1',
|
||||
port: 2222,
|
||||
private_key_path: ['/priv/key/path'],
|
||||
username: 'vagrant',
|
||||
password: 'vagrant',
|
||||
keys_only: true,
|
||||
paranoid: false
|
||||
)
|
||||
end
|
||||
|
||||
it "has password defined" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
anything, anything, hash_including(
|
||||
password: 'vagrant'
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "includes private key paths" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
anything, anything, hash_including(
|
||||
keys: ["/priv/key/path"]
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
|
||||
it "includes `publickey` and `password` auth methods" do
|
||||
expect(Net::SSH).to receive(:start).with(
|
||||
anything, anything, hash_including(
|
||||
auth_methods: ["none", "hostbased", "publickey", "password"]
|
||||
)
|
||||
).and_return(true)
|
||||
communicator.send(:connect)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".generate_environment_export" do
|
||||
it "should generate bourne shell compatible export" do
|
||||
communicator.send(:generate_environment_export, "TEST", "value").should eq("export TEST=\"value\"\n")
|
||||
end
|
||||
|
||||
context "with custom template defined" do
|
||||
let(:export_command_template){ "setenv %ENV_KEY% %ENV_VALUE%" }
|
||||
|
||||
it "should generate custom export based on template" do
|
||||
communicator.send(:generate_environment_export, "TEST", "value").should eq("setenv TEST value\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,163 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "config.winssh - Vagrantfile"
|
||||
sidebar_current: "vagrantfile-winssh"
|
||||
description: |-
|
||||
The settings within "config.winssh" relate to configuring how Vagrant
|
||||
will access your machine over Windows OpenSSH. As with most Vagrant settings, the
|
||||
defaults are typically fine, but you can fine tune whatever you would like.
|
||||
---
|
||||
|
||||
# WinSSH
|
||||
|
||||
The WinSSH communicator is built specifically for the Windows native
|
||||
port of OpenSSH. It does not rely on a POSIX-like environment which
|
||||
removes the requirement of extra software installation (like cygwin)
|
||||
for proper functionality.
|
||||
|
||||
_NOTE: The Windows native port of OpenSSH is still considered
|
||||
"pre-release" and is non-production ready._
|
||||
|
||||
For more information, see the [Win32-OpenSSH project page](https://github.com/PowerShell/Win32-OpenSSH/).
|
||||
|
||||
# WinSSH Settings
|
||||
|
||||
The WinSSH communicator uses the same connection configuration options
|
||||
as the SSH communicator. These settings provide the information for the
|
||||
communicator to establish a connection to the VM.
|
||||
|
||||
**Config namespace: `config.ssh`**
|
||||
|
||||
The settings within `config.ssh` relate to configuring how Vagrant
|
||||
will access your machine over SSH. As with most Vagrant settings, the
|
||||
defaults are typically fine, but you can fine tune whatever you would like.
|
||||
|
||||
## Available Settings
|
||||
|
||||
`config.ssh.username` - This sets the username that Vagrant will SSH
|
||||
as by default. Providers are free to override this if they detect a more
|
||||
appropriate user. By default this is "vagrant," since that is what most
|
||||
public boxes are made as.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.password` - This sets a password that Vagrant will use to
|
||||
authenticate the SSH user. Note that Vagrant recommends you use key-based
|
||||
authentication rather than a password (see `private_key_path`) below. If
|
||||
you use a password, Vagrant will automatically insert a keypair if
|
||||
`insert_key` is true.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.host` - The hostname or IP to SSH into. By default this is
|
||||
empty, because the provider usually figures this out for you.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.port` - The port to SSH into. By default this is port 22.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.guest_port` - The port on the guest that SSH is running on. This
|
||||
is used by some providers to detect forwarded ports for SSH. For example, if
|
||||
this is set to 22 (the default), and Vagrant detects a forwarded port to
|
||||
port 22 on the guest from port 4567 on the host, Vagrant will attempt
|
||||
to use port 4567 to talk to the guest if there is no other option.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.private_key_path` - The path to the private key to use to
|
||||
SSH into the guest machine. By default this is the insecure private key
|
||||
that ships with Vagrant, since that is what public boxes use. If you make
|
||||
your own custom box with a custom SSH key, this should point to that
|
||||
private key.
|
||||
|
||||
You can also specify multiple private keys by setting this to be an array.
|
||||
This is useful, for example, if you use the default private key to bootstrap
|
||||
the machine, but replace it with perhaps a more secure key later.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.insert_key` - If `true`, Vagrant will automatically insert
|
||||
a keypair to use for SSH, replacing Vagrant's default insecure key
|
||||
inside the machine if detected. By default, this is true.
|
||||
|
||||
This only has an effect if you do not already use private keys for
|
||||
authentication or if you are relying on the default insecure key.
|
||||
If you do not have to care about security in your project and want to
|
||||
keep using the default insecure key, set this to `false`.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.keys_only` - Only use Vagrant-provided SSH private keys (do not use
|
||||
any keys stored in ssh-agent). The default value is `true`.`
|
||||
|
||||
<hr>
|
||||
|
||||
`config.ssh.paranoid` - Perform strict host-key verification. The default value
|
||||
is `false`.
|
||||
|
||||
# WinSSH Settings
|
||||
|
||||
The configuration options below are specific to the WinSSH communicator.
|
||||
|
||||
**Config namespace: `config.winssh`**
|
||||
|
||||
## Available Settings
|
||||
|
||||
`config.winssh.forward_agent` - If `true`, agent forwarding over SSH
|
||||
connections is enabled. Defaults to false.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.winssh.forward_env` - An array of host environment variables to forward to
|
||||
the guest. If you are familiar with OpenSSH, this corresponds to the `SendEnv`
|
||||
parameter.
|
||||
|
||||
```ruby
|
||||
config.winssh.forward_env = ["CUSTOM_VAR"]
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
`config.winssh.proxy_command` - A command-line command to execute that receives
|
||||
the data to send to SSH on stdin. This can be used to proxy the SSH connection.
|
||||
`%h` in the command is replaced with the host and `%p` is replaced with
|
||||
the port.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.winssh.keep_alive` If `true`, this setting SSH will send keep-alive packets
|
||||
every 5 seconds by default to keep connections alive.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.winssh.shell` - The shell to use when executing SSH commands from
|
||||
Vagrant. By default this is `cmd`. Valid values are `"cmd"` or `"powershell"`.
|
||||
Note that this has no effect on the shell you get when you run `vagrant ssh`.
|
||||
This configuration option only affects the shell to use when executing commands
|
||||
internally in Vagrant.
|
||||
|
||||
<hr>
|
||||
|
||||
`config.winssh.export_command_template` - The template used to generate
|
||||
exported environment variables in the active session. This can be useful
|
||||
when using a Bourne incompatible shell like C shell. The template supports
|
||||
two variables which are replaced with the desired environment variable key and
|
||||
environment variable value: `%ENV_KEY%` and `%ENV_VALUE%`. The default template
|
||||
for a `cmd` configured shell is:
|
||||
|
||||
```ruby
|
||||
config.winssh.export_command_template = 'set %ENV_KEY%="%ENV_VALUE%"'
|
||||
```
|
||||
|
||||
The default template for a `powershell` configured shell is:
|
||||
|
||||
```ruby
|
||||
config.winssh.export_command_template = '$env:%ENV_KEY%="%ENV_VALUE%"'
|
||||
```
|
||||
|
||||
`config.winssh.sudo_command` - The command to use when executing a command
|
||||
with `sudo`. This defaults to `%c` (assumes vagrant user is an administator
|
||||
and needs no escalation). The `%c` will be replaced by the command that is
|
||||
being executed.
|
|
@ -69,6 +69,7 @@
|
|||
<li<%= sidebar_current("vagrantfile-machine") %>><a href="/docs/vagrantfile/machine_settings.html"><tt>config.vm</tt></a></li>
|
||||
<li<%= sidebar_current("vagrantfile-ssh") %>><a href="/docs/vagrantfile/ssh_settings.html"><tt>config.ssh</tt></a></li>
|
||||
<li<%= sidebar_current("vagrantfile-winrm") %>><a href="/docs/vagrantfile/winrm_settings.html"><tt>config.winrm</tt></a></li>
|
||||
<li<%= sidebar_current("vagrantfile-winssh") %>><a href="/docs/vagrantfile/winssh_settings.html"><tt>config.winssh</tt></a></li>
|
||||
<li<%= sidebar_current("vagrantfile-vagrant") %>><a href="/docs/vagrantfile/vagrant_settings.html"><tt>config.vagrant</tt></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue