554 lines
18 KiB
Ruby
554 lines
18 KiB
Ruby
require_relative "../../../base"
|
|
|
|
require "vagrant/util/platform"
|
|
|
|
require Vagrant.source_root.join("plugins/synced_folders/rsync/helper")
|
|
|
|
describe VagrantPlugins::SyncedFolderRSync::RsyncHelper do
|
|
include_context "unit"
|
|
|
|
let(:iso_env) do
|
|
# We have to create a Vagrantfile so there is a root path
|
|
env = isolated_environment
|
|
env.vagrantfile("")
|
|
env.create_vagrant_env
|
|
end
|
|
|
|
let(:guest) { double("guest") }
|
|
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
|
|
|
|
subject { described_class }
|
|
|
|
before do
|
|
allow(machine).to receive(:guest).and_return(guest)
|
|
|
|
# Don't do all the crazy Cygwin stuff
|
|
allow(Vagrant::Util::Platform).to receive(:cygwin_path) do |path, **opts|
|
|
path
|
|
end
|
|
end
|
|
|
|
describe "#exclude_to_regexp" do
|
|
let(:path) { "/foo/bar" }
|
|
|
|
it "converts a directory match" do
|
|
expect(described_class.exclude_to_regexp("foo/")).
|
|
to eq(/^foo\/.[^\/]*/)
|
|
end
|
|
|
|
it "converts the start anchor" do
|
|
expect(described_class.exclude_to_regexp("/foo")).
|
|
to eq(/^foo\//)
|
|
end
|
|
|
|
it "converts the **" do
|
|
expect(described_class.exclude_to_regexp("fo**o")).
|
|
to eq(/^fo.*o\/.[^\/]*/)
|
|
end
|
|
|
|
it "converts the *" do
|
|
expect(described_class.exclude_to_regexp("fo*o")).
|
|
to eq(/^fo[^\/]*o\/.[^\/]*/)
|
|
end
|
|
end
|
|
|
|
describe "#rsync_single" do
|
|
let(:result) { Vagrant::Util::Subprocess::Result.new(0, "", "") }
|
|
|
|
let(:ssh_info) {{
|
|
private_key_path: [],
|
|
}}
|
|
let(:opts) {{
|
|
hostpath: "/foo",
|
|
}}
|
|
let(:ui) { machine.ui }
|
|
|
|
before do
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute){ result }
|
|
|
|
allow(guest).to receive(:capability?){ false }
|
|
end
|
|
|
|
it "doesn't raise an error if it succeeds" do
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "doesn't call cygwin_path on non-Windows" do
|
|
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)
|
|
expect(Vagrant::Util::Platform).not_to receive(:cygwin_path)
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "calls cygwin_path on Windows" do
|
|
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
|
|
expect(Vagrant::Util::Platform).to receive(:cygwin_path).and_return("foo")
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[args.length - 3]).to eql("foo/")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "raises an error if the exit code is non-zero" do
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute)
|
|
.and_return(Vagrant::Util::Subprocess::Result.new(1, "", ""))
|
|
|
|
expect {subject.rsync_single(machine, ssh_info, opts) }.
|
|
to raise_error(Vagrant::Errors::RSyncError)
|
|
end
|
|
|
|
context "host and guest paths" do
|
|
it "syncs the hostpath to the guest path" do
|
|
opts[:hostpath] = "/foo"
|
|
opts[:guestpath] = "/bar"
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expected = Vagrant::Util::Platform.fs_real_path("/foo").to_s
|
|
expect(args[args.length - 3]).to eql("#{expected}/")
|
|
expect(args[args.length - 2]).to include("/bar")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "expands the hostpath relative to the root path" do
|
|
opts[:hostpath] = "foo"
|
|
opts[:guestpath] = "/bar"
|
|
|
|
hostpath_expanded = File.expand_path(opts[:hostpath], machine.env.root_path)
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[args.length - 3]).to eql("#{hostpath_expanded}/")
|
|
expect(args[args.length - 2]).to include("/bar")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
|
|
it "executes within the root path" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args.last).to be_kind_of(Hash)
|
|
|
|
opts = args.last
|
|
expect(opts[:workdir]).to eql(machine.env.root_path.to_s)
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "executes the rsync_pre capability first if it exists" do
|
|
expect(guest).to receive(:capability?).with(:rsync_pre).and_return(true)
|
|
expect(guest).to receive(:capability).with(:rsync_pre, opts).ordered
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).ordered.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "executes the rsync_post capability after if it exists" do
|
|
expect(guest).to receive(:capability?).with(:rsync_post).and_return(true)
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).ordered.and_return(result)
|
|
expect(guest).to receive(:capability).with(:rsync_post, opts).ordered
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
context "with rsync_post capability" do
|
|
before do
|
|
allow(guest).to receive(:capability?).with(:rsync_post).and_return(true)
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result)
|
|
end
|
|
|
|
it "should raise custom error when capability errors" do
|
|
expect(guest).to receive(:capability).with(:rsync_post, opts).
|
|
and_raise(Vagrant::Errors::VagrantError)
|
|
|
|
expect { subject.rsync_single(machine, ssh_info, opts) }.
|
|
to raise_error(Vagrant::Errors::RSyncPostCommandError)
|
|
end
|
|
end
|
|
|
|
context "with rsync_ownership option" do
|
|
let(:rsync_local_version) { "3.1.1" }
|
|
let(:rsync_remote_version) { "3.1.1" }
|
|
let(:rsync_result) { Vagrant::Util::Subprocess::Result.new(0, "", "") }
|
|
|
|
before do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).
|
|
with("rsync", "--version").and_return(Vagrant::Util::Subprocess::Result.new(0, " version #{rsync_local_version} ", ""))
|
|
allow(machine.communicate).to receive(:execute).with(/--version/).and_yield(:stdout, " version #{rsync_remote_version} ")
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).with("rsync", any_args).and_return(rsync_result)
|
|
opts[:rsync_ownership] = true
|
|
end
|
|
|
|
after { subject.reset! }
|
|
|
|
it "should use the rsync --chown flag" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
expect(args.detect{|a| a.include?("--chown")}).to be_truthy
|
|
rsync_result
|
|
}
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "should set the chown option to false" do
|
|
expect(opts.has_key?(:chown)).to eq(false)
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
expect(opts[:chown]).to eq(false)
|
|
end
|
|
|
|
context "when local rsync version does not support --chown" do
|
|
let(:rsync_local_version) { "2.0" }
|
|
|
|
it "should not use the --chown flag" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
expect(args.detect{|a| a.include?("--chown")}).to be_falsey
|
|
rsync_result
|
|
}
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
|
|
context "when remote rsync version does not support --chown" do
|
|
let(:rsync_remote_version) { "2.0" }
|
|
|
|
it "should not use the --chown flag" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
expect(args.detect{|a| a.include?("--chown")}).to be_falsey
|
|
rsync_result
|
|
}
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "excluding files" do
|
|
it "excludes files if given as a string" do
|
|
opts[:exclude] = "foo"
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
index = args.find_index("foo")
|
|
expect(index).to be > 0
|
|
expect(args[index-1]).to eql("--exclude")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "excludes multiple files" do
|
|
opts[:exclude] = ["foo", "bar"]
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
index = args.find_index("foo")
|
|
expect(index).to be > 0
|
|
expect(args[index-1]).to eql("--exclude")
|
|
|
|
index = args.find_index("bar")
|
|
expect(index).to be > 0
|
|
expect(args[index-1]).to eql("--exclude")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
|
|
context "custom arguments" do
|
|
it "uses the default arguments if not given" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[1]).to eq("--verbose")
|
|
expect(args[2]).to eq("--archive")
|
|
expect(args[3]).to eq("--delete")
|
|
|
|
expected = Vagrant::Util::Platform.fs_real_path("/foo").to_s
|
|
expect(args[args.length - 3]).to eql("#{expected}/")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "uses the custom arguments if given" do
|
|
opts[:args] = ["--verbose", "-z"]
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[1]).to eq("--verbose")
|
|
expect(args[2]).to eq("-z")
|
|
|
|
expected = Vagrant::Util::Platform.fs_real_path("/foo").to_s
|
|
expect(args[args.length - 3]).to eql("#{expected}/")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
|
|
context "control sockets" do
|
|
it "creates a tmp dir" do
|
|
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)
|
|
allow(Dir).to receive(:mktmpdir).with("vagrant-rsync-").
|
|
and_return("/tmp/vagrant-rsync-12345")
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[9]).to include("ControlPath=/tmp/vagrant-rsync-12345")
|
|
}.and_return(result)
|
|
|
|
expect(FileUtils).to receive(:remove_entry_secure).with("/tmp/vagrant-rsync-12345", true).and_return(true)
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "does not create tmp dir on windows platforms" do
|
|
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
|
|
allow(Dir).to receive(:mktmpdir).with("vagrant-rsync-").
|
|
and_return("/tmp/vagrant-rsync-12345")
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args).not_to include("ControlPath=/tmp/vagrant-rsync-12345")
|
|
}.and_return(result)
|
|
|
|
expect(FileUtils).not_to receive(:remove_entry_secure).with("/tmp/vagrant-rsync-12345", true)
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#rsync_single with custom ssh_info" do
|
|
let(:result) { Vagrant::Util::Subprocess::Result.new(0, "", "") }
|
|
|
|
let(:ssh_info) {{
|
|
:private_key_path => ['/path/to/key'],
|
|
:keys_only => true,
|
|
:verify_host_key => false,
|
|
}}
|
|
let(:opts) {{
|
|
hostpath: "/foo",
|
|
}}
|
|
let(:ui) { machine.ui }
|
|
|
|
before do
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute){ result }
|
|
|
|
allow(guest).to receive(:capability?){ false }
|
|
end
|
|
|
|
context "with an IPv6 address" do
|
|
before { ssh_info[:host] = "fe00::0" }
|
|
|
|
it "formats the address correctly" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, "@[#{ssh_info[:host]}]:''", instance_of(Hash))
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
|
|
context "with an IPv4 address" do
|
|
before { ssh_info[:host] = "127.0.0.1" }
|
|
|
|
it "formats the address correctly" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, "@#{ssh_info[:host]}:''", instance_of(Hash))
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
|
|
it "includes IdentitiesOnly, StrictHostKeyChecking, and UserKnownHostsFile with defaults" do
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[9]).to include('IdentitiesOnly')
|
|
expect(args[9]).to include('StrictHostKeyChecking')
|
|
expect(args[9]).to include('UserKnownHostsFile')
|
|
expect(args[9]).to include("-i '/path/to/key'")
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "includes StrictHostKeyChecking, and UserKnownHostsFile when verify_host_key is false" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[9]).to include('StrictHostKeyChecking')
|
|
expect(args[9]).to include('UserKnownHostsFile')
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "includes StrictHostKeyChecking, and UserKnownHostsFile when verify_host_key is :never" do
|
|
ssh_info[:verify_host_key] = :never
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
|
expect(args[9]).to include('StrictHostKeyChecking')
|
|
expect(args[9]).to include('UserKnownHostsFile')
|
|
}.and_return(result)
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "omits IdentitiesOnly with keys_only = false" do
|
|
ssh_info[:keys_only] = false
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
|
|
expect(args[9]).not_to include('IdentitiesOnly')
|
|
result
|
|
end
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "omits StrictHostKeyChecking and UserKnownHostsFile with paranoid = true" do
|
|
ssh_info[:keys_only] = false
|
|
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
|
|
expect(args[9]).not_to include('StrictHostKeyChecking ')
|
|
expect(args[9]).not_to include('UserKnownHostsFile ')
|
|
result
|
|
end
|
|
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
|
|
it "includes custom ssh config when set" do
|
|
ssh_info[:config] = "/path/to/ssh/config"
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
|
|
ssh_config_args = "-F /path/to/ssh/config"
|
|
expect(args.any?{|a| a.include?(ssh_config_args)}).to be_truthy
|
|
result
|
|
end
|
|
subject.rsync_single(machine, ssh_info, opts)
|
|
end
|
|
end
|
|
|
|
describe ".rsync_chown_support?" do
|
|
let(:local_version) { "3.1.1" }
|
|
let(:remote_version) { "3.1.1" }
|
|
|
|
before do
|
|
allow(subject).to receive(:local_rsync_version).and_return(local_version)
|
|
allow(subject).to receive(:machine_rsync_version).and_return(remote_version)
|
|
end
|
|
|
|
it "should return when local and remote versions support chown" do
|
|
expect(subject.rsync_chown_support?(machine)).to be_truthy
|
|
end
|
|
|
|
context "when local version does not support chown" do
|
|
let(:local_version) { "2.0" }
|
|
|
|
it "should return false" do
|
|
expect(subject.rsync_chown_support?(machine)).to be_falsey
|
|
end
|
|
end
|
|
|
|
context "when remote version does not support chown" do
|
|
let(:remote_version) { "2.0" }
|
|
|
|
it "should return false" do
|
|
expect(subject.rsync_chown_support?(machine)).to be_falsey
|
|
end
|
|
end
|
|
|
|
context "when both local and remote versions do not support chown" do
|
|
let(:local_version) { "2.0" }
|
|
let(:remote_version) { "2.0" }
|
|
|
|
it "should return false" do
|
|
expect(subject.rsync_chown_support?(machine)).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".machine_rsync_version" do
|
|
let(:version_output) {
|
|
<<-EOV
|
|
rsync version 3.1.3 protocol version 31
|
|
Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.
|
|
Web site: http://rsync.samba.org/
|
|
Capabilities:
|
|
64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
|
|
socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
|
|
append, ACLs, xattrs, iconv, symtimes, prealloc
|
|
|
|
rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you
|
|
are welcome to redistribute it under certain conditions. See the GNU
|
|
General Public Licence for details.
|
|
EOV
|
|
}
|
|
|
|
before do
|
|
allow(machine.communicate).to receive(:execute).with(/--version/).
|
|
and_yield(:stdout, version_output)
|
|
allow(guest).to receive(:capability?).and_return(false)
|
|
end
|
|
|
|
it "should extract the version string" do
|
|
expect(subject.machine_rsync_version(machine)).to eq("3.1.3")
|
|
end
|
|
|
|
context "when version output is an unknown format" do
|
|
let(:version_output) { "unknown" }
|
|
|
|
it "should return nil value" do
|
|
expect(subject.machine_rsync_version(machine)).to be_nil
|
|
end
|
|
end
|
|
|
|
context "with guest rsync_command capability" do
|
|
let(:rsync_path) { "custom_rsync" }
|
|
|
|
before do
|
|
allow(guest).to receive(:capability?).with(:rsync_command).
|
|
and_return(true)
|
|
allow(guest).to receive(:capability).with(:rsync_command).
|
|
and_return(rsync_path)
|
|
end
|
|
|
|
it "should use custom rsync_path" do
|
|
expect(machine.communicate).to receive(:execute).
|
|
with("#{rsync_path} --version").and_yield(:stdout, version_output)
|
|
subject.machine_rsync_version(machine)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".local_rsync_version" do
|
|
let(:version_output) {
|
|
<<-EOV
|
|
rsync version 3.1.3 protocol version 31
|
|
Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.
|
|
Web site: http://rsync.samba.org/
|
|
Capabilities:
|
|
64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
|
|
socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
|
|
append, ACLs, xattrs, iconv, symtimes, prealloc
|
|
|
|
rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you
|
|
are welcome to redistribute it under certain conditions. See the GNU
|
|
General Public Licence for details.
|
|
EOV
|
|
}
|
|
let(:result) { Vagrant::Util::Subprocess::Result.new(0, version_output, "") }
|
|
|
|
before do
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).with("rsync", "--version").
|
|
and_return(result)
|
|
end
|
|
|
|
after { subject.reset! }
|
|
|
|
it "should extract the version string" do
|
|
expect(subject.local_rsync_version).to eq("3.1.3")
|
|
end
|
|
|
|
it "should cache the version lookup" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with("rsync", "--version").
|
|
and_return(result).once
|
|
expect(subject.local_rsync_version).to eq("3.1.3")
|
|
expect(subject.local_rsync_version).to eq("3.1.3")
|
|
end
|
|
|
|
context "when version output is an unknown format" do
|
|
let(:version_output) { "unknown" }
|
|
|
|
it "should return nil value" do
|
|
expect(subject.local_rsync_version).to be_nil
|
|
end
|
|
end
|
|
end
|
|
end
|