This update was prompted by updates in openssh to the scp behavior making source directory paths suffixed with `.` no longer valid resulting in errors on upload. The upload implementation within the ssh communicator has been updated to retain the existing behavior. Included in this update is modifications to the winrm communicator so the upload functionality matches that of the ssh communicator respecting the trailing `.` behavior on source paths. With the communicators updated to properly handle the paths, the file provisioner was also updated to simply apply previously defined path update rules only. Fixes #10675
273 lines
9.4 KiB
Ruby
273 lines
9.4 KiB
Ruby
require File.expand_path("../../../../base", __FILE__)
|
|
|
|
require Vagrant.source_root.join("plugins/communicators/winrm/shell")
|
|
require Vagrant.source_root.join("plugins/communicators/winrm/config")
|
|
|
|
describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
|
|
include_context "unit"
|
|
|
|
let(:connection) { double("winrm_connection") }
|
|
let(:shell) { double("winrm_shell") }
|
|
let(:port) { config.transport == :ssl ? 5986 : 5985 }
|
|
let(:config) {
|
|
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
|
|
c.username = 'username'
|
|
c.password = 'password'
|
|
c.max_tries = 3
|
|
c.retry_delay = 0
|
|
c.basic_auth_only = false
|
|
c.retry_delay = 1
|
|
c.max_tries = 2
|
|
c.finalize!
|
|
end
|
|
}
|
|
let(:output) { WinRM::Output.new.tap { |out| out.exitcode = 0 } }
|
|
|
|
before { allow(connection).to receive(:shell).and_yield(shell) }
|
|
|
|
subject do
|
|
described_class.new('localhost', port, config).tap do |comm|
|
|
allow(comm).to receive(:new_connection).and_return(connection)
|
|
end
|
|
end
|
|
|
|
describe "#upload" do
|
|
let(:fm) { double("file_manager") }
|
|
|
|
before do
|
|
allow(WinRM::FS::FileManager).to receive(:new).with(connection)
|
|
.and_return(fm)
|
|
end
|
|
|
|
it "should call file_manager.upload for each passed in path" do
|
|
from = ["/path", "/path/folder", "/path/folder/file.py"]
|
|
to = "/destination"
|
|
size = 80
|
|
|
|
allow(fm).to receive(:upload).and_return(size)
|
|
|
|
expect(fm).to receive(:upload).exactly(from.size).times
|
|
expect(subject.upload(from, to)).to eq(size*from.size)
|
|
end
|
|
|
|
it "should call file_manager.upload once for a single path" do
|
|
from = "/path/folder/file.py"
|
|
to = "/destination"
|
|
size = 80
|
|
|
|
allow(fm).to receive(:upload).and_return(size)
|
|
|
|
expect(fm).to receive(:upload).exactly(1).times
|
|
expect(subject.upload(from, to)).to eq(size)
|
|
end
|
|
|
|
context "when source is a directory" do
|
|
let(:source) { "path/sourcedir" }
|
|
|
|
before do
|
|
allow(File).to receive(:directory?).with(/#{Regexp.escape(source)}/).and_return(true)
|
|
end
|
|
|
|
it "should add source directory name to destination" do
|
|
expect(fm).to receive(:upload) do |from, to|
|
|
expect(to).to include("sourcedir")
|
|
end
|
|
subject.upload(source, "/dest")
|
|
end
|
|
|
|
it "should not add source directory name to destination when source ends with '.'" do
|
|
source << "/."
|
|
expect(fm).to receive(:upload) do |from, to|
|
|
expect(to).to eq("/dest")
|
|
end
|
|
subject.upload(source, "/dest")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".powershell" do
|
|
it "should call winrm powershell" do
|
|
expect(shell).to receive(:run).with("dir").and_return(output)
|
|
expect(subject.powershell("dir").exitcode).to eq(0)
|
|
end
|
|
|
|
it "should raise an execution error when an exception occurs" do
|
|
expect(shell).to receive(:run).with("dir").and_raise(
|
|
StandardError.new("Oh no! a 500 SOAP error!"))
|
|
expect { subject.powershell("dir") }.to raise_error(
|
|
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
|
|
end
|
|
end
|
|
|
|
describe ".elevated" do
|
|
let(:eusername) { double("elevatedusername") }
|
|
let(:username) { double("username") }
|
|
let(:failed_output) { WinRM::Output.new.tap { |out|
|
|
out.exitcode = -196608
|
|
out << {stderr: "(10,8):UserId:"}
|
|
out << {stderr: "At line:72 char:1"}
|
|
} }
|
|
|
|
before do
|
|
allow(subject).to receive(:elevated_username).and_return(eusername)
|
|
allow(shell).to receive(:username).and_return(username)
|
|
allow(shell).to receive(:username=)
|
|
end
|
|
|
|
it "should call winrm elevated" do
|
|
expect(shell).to receive(:run).with("dir").and_return(output)
|
|
expect(shell).to receive(:interactive_logon=).with(false)
|
|
expect(subject.elevated("dir").exitcode).to eq(0)
|
|
end
|
|
|
|
it "should set interactive_logon when interactive is true" do
|
|
expect(shell).to receive(:run).with("dir").and_return(output)
|
|
expect(shell).to receive(:interactive_logon=).with(true)
|
|
expect(subject.elevated("dir", { interactive: true }).exitcode).to eq(0)
|
|
end
|
|
|
|
it "should raise an execution error when an exception occurs" do
|
|
expect(shell).to receive(:run).with("dir").and_raise(
|
|
StandardError.new("Oh no! a 500 SOAP error!"))
|
|
expect { subject.powershell("dir") }.to raise_error(
|
|
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
|
|
end
|
|
|
|
it "should use elevated username and retry on username failure" do
|
|
expect(subject).to receive(:elevated_username).and_return(eusername)
|
|
expect(shell).to receive(:run).with("dir").and_return(failed_output)
|
|
expect(shell).to receive(:run).with("dir").and_return(output)
|
|
expect(shell).to receive(:interactive_logon=).with(false)
|
|
expect(subject.elevated("dir").exitcode).to eq(0)
|
|
end
|
|
|
|
it "should not retry on username failure if elevated username is the same" do
|
|
expect(subject).to receive(:elevated_username).and_return(username)
|
|
expect(shell).to receive(:run).with("dir").and_return(failed_output)
|
|
expect(shell).to receive(:interactive_logon=).with(false)
|
|
expect(subject.elevated("dir").exitcode).to eq(failed_output.exitcode)
|
|
end
|
|
end
|
|
|
|
describe ".cmd" do
|
|
it "should call winrm cmd" do
|
|
expect(connection).to receive(:shell).with(:cmd, { })
|
|
expect(shell).to receive(:run).with("dir").and_return(output)
|
|
expect(subject.cmd("dir").exitcode).to eq(0)
|
|
end
|
|
|
|
context "when codepage is given" do
|
|
let(:config) {
|
|
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
|
|
c.codepage = 800
|
|
c.finalize!
|
|
end
|
|
}
|
|
|
|
it "creates shell with the given codepage when set" do
|
|
expect(connection).to receive(:shell).with(:cmd, { codepage: 800 })
|
|
expect(shell).to receive(:run).with("dir").and_return(output)
|
|
expect(subject.cmd("dir").exitcode).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".wql" do
|
|
it "should call winrm wql" do
|
|
expect(connection).to receive(:run_wql).with("select * from Win32_OperatingSystem")
|
|
subject.wql("select * from Win32_OperatingSystem")
|
|
end
|
|
|
|
it "should retry when a WinRMAuthorizationError is received" do
|
|
expect(connection).to receive(:run_wql).with("select * from Win32_OperatingSystem").exactly(2).times.and_raise(
|
|
# Note: The initialize for WinRMAuthorizationError may require a status_code as
|
|
# the second argument in a future WinRM release. Currently it doesn't track the
|
|
# status code.
|
|
WinRM::WinRMAuthorizationError.new("Oh no!! Unauthorized")
|
|
)
|
|
expect { subject.wql("select * from Win32_OperatingSystem") }.to raise_error(
|
|
VagrantPlugins::CommunicatorWinRM::Errors::AuthenticationFailed)
|
|
end
|
|
end
|
|
|
|
describe ".endpoint" do
|
|
context 'when transport is :ssl' do
|
|
let(:config) {
|
|
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
|
|
c.transport = :ssl
|
|
c.finalize!
|
|
end
|
|
}
|
|
it "should create winrm endpoint address using https" do
|
|
expect(subject.send(:endpoint)).to eq("https://localhost:5986/wsman")
|
|
end
|
|
end
|
|
|
|
context "when transport is :negotiate" do
|
|
it "should create winrm endpoint address using http" do
|
|
expect(subject.send(:endpoint)).to eq("http://localhost:5985/wsman")
|
|
end
|
|
end
|
|
|
|
context "when transport is :plaintext" do
|
|
let(:config) {
|
|
VagrantPlugins::CommunicatorWinRM::Config.new.tap do |c|
|
|
c.transport = :plaintext
|
|
c.finalize!
|
|
end
|
|
}
|
|
it "should create winrm endpoint address using http" do
|
|
expect(subject.send(:endpoint)).to eq("http://localhost:5985/wsman")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".endpoint_options" do
|
|
it "should create endpoint options" do
|
|
expect(subject.send(:endpoint_options)).to eq(
|
|
{ endpoint: "http://localhost:5985/wsman", operation_timeout: 1800,
|
|
user: "username", password: "password", host: "localhost", port: 5985,
|
|
basic_auth_only: false, no_ssl_peer_verification: false,
|
|
retry_delay: 1, retry_limit: 2, transport: :negotiate })
|
|
end
|
|
end
|
|
|
|
describe "#elevated_username" do
|
|
let(:username) { "username" }
|
|
|
|
before do
|
|
allow(subject).to receive(:username).and_return(username)
|
|
allow(subject).to receive(:powershell)
|
|
end
|
|
|
|
it "should return username" do
|
|
expect(subject.send(:elevated_username)).to eq(username)
|
|
end
|
|
|
|
it "should attempt to get computer name" do
|
|
expect(subject).to receive(:powershell).with(/computername/)
|
|
subject.send(:elevated_username)
|
|
end
|
|
|
|
it "should prepend computer name when available" do
|
|
expect(subject).to receive(:powershell).with(/computername/).and_yield(:stdout, "COMPUTERNAME")
|
|
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
|
|
end
|
|
|
|
it "should compute elevated username every time" do
|
|
expect(subject).to receive(:powershell).twice.with(/computername/).and_yield(:stdout, "COMPUTERNAME")
|
|
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
|
|
expect(subject.send(:elevated_username)).to eq("COMPUTERNAME\\#{username}")
|
|
end
|
|
|
|
context "when username includes computer/domain name" do
|
|
let(:username) { "machine\\username" }
|
|
|
|
it "should not attempt to get computer name" do
|
|
expect(subject).not_to receive(:powershell)
|
|
expect(subject.send(:elevated_username)).to eq(username)
|
|
end
|
|
end
|
|
end
|
|
end
|