diff --git a/plugins/hosts/bsd/cap/nfs.rb b/plugins/hosts/bsd/cap/nfs.rb index c872b4e24..d25592ba6 100644 --- a/plugins/hosts/bsd/cap/nfs.rb +++ b/plugins/hosts/bsd/cap/nfs.rb @@ -26,8 +26,8 @@ module VagrantPlugins logger.debug("Compiling map of sub-directories for NFS exports...") dirmap = {} folders.sort_by { |_, opts| opts[:hostpath] }.each do |_, opts| + opts[:hostpath] = environment.host.capability(:resolve_host_path, opts[:hostpath].gsub('"', '\"')) hostpath = opts[:hostpath].dup - hostpath.gsub!('"', '\"') found = false dirmap.each do |dirs, diropts| diff --git a/plugins/hosts/bsd/cap/path.rb b/plugins/hosts/bsd/cap/path.rb new file mode 100644 index 000000000..25ca3cd15 --- /dev/null +++ b/plugins/hosts/bsd/cap/path.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module HostBSD + module Cap + class Path + def self.resolve_host_path(env, path) + path + end + end + end + end +end diff --git a/plugins/hosts/bsd/plugin.rb b/plugins/hosts/bsd/plugin.rb index ce79ede31..107157675 100644 --- a/plugins/hosts/bsd/plugin.rb +++ b/plugins/hosts/bsd/plugin.rb @@ -36,6 +36,11 @@ module VagrantPlugins Cap::NFS end + host_capability("bsd", "resolve_host_path") do + require_relative "cap/path" + Cap::Path + end + host_capability("bsd", "set_ssh_key_permissions") do require_relative "cap/ssh" Cap::SSH diff --git a/plugins/hosts/darwin/cap/path.rb b/plugins/hosts/darwin/cap/path.rb new file mode 100644 index 000000000..c39d63319 --- /dev/null +++ b/plugins/hosts/darwin/cap/path.rb @@ -0,0 +1,58 @@ +module VagrantPlugins + module HostDarwin + module Cap + class Path + @@logger = Log4r::Logger.new("vagrant::host::darwin::path") + + FIRMLINK_DEFS = "/usr/share/firmlinks".freeze + FIRMLINK_DATA_PATH = "/System/Volumes/Data".freeze + + # Resolve the given host path to the actual + # usable system path by detecting firmlinks + # if available on the current system + # + # @param [String] path Host system path + # @return [String] resolved path + def self.resolve_host_path(env, path) + path = File.expand_path(path) + firmlink = firmlink_map.detect do |mount_path, data_path| + path.start_with?(mount_path) + end + return path if firmlink.nil? + current_prefix, new_suffix = firmlink + new_prefix = File.join(FIRMLINK_DATA_PATH, new_suffix) + new_path = path.sub(current_prefix, new_prefix) + @@logger.debug("Resolved given path `#{path}` to `#{new_path}`") + new_path + end + + # Generate mapping of firmlinks if available on the host + # + # @return [Hash] + def self.firmlink_map + if !@firmlink_map + return @firmlink_map = {} if !File.exist?(FIRMLINK_DEFS) + begin + @firmlink_map = Hash[ + File.readlines(FIRMLINK_DEFS).map { |d| + d.strip.split(/\s+/, 2) + } + ] + rescue => err + @@logger.warn("Failed to parse firmlink definitions: #{err}") + @firmlink_map = {} + end + end + @firmlink_map + end + + # @private + # Reset the cached values for capability. This is not considered a public + # API and should only be used for testing. + def self.reset! + instance_variables.each(&method(:remove_instance_variable)) + end + end + end + end +end diff --git a/plugins/hosts/darwin/plugin.rb b/plugins/hosts/darwin/plugin.rb index 7b08af3f1..b6c535c9b 100644 --- a/plugins/hosts/darwin/plugin.rb +++ b/plugins/hosts/darwin/plugin.rb @@ -16,6 +16,11 @@ module VagrantPlugins Cap::ProviderInstallVirtualBox end + host_capability("darwin", "resolve_host_path") do + require_relative "cap/path" + Cap::Path + end + host_capability("darwin", "rdp_client") do require_relative "cap/rdp" Cap::RDP diff --git a/test/unit/plugins/hosts/bsd/cap/nfs_test.rb b/test/unit/plugins/hosts/bsd/cap/nfs_test.rb new file mode 100644 index 000000000..51c125484 --- /dev/null +++ b/test/unit/plugins/hosts/bsd/cap/nfs_test.rb @@ -0,0 +1,48 @@ +require_relative "../../../../base" +require_relative "../../../../../../plugins/hosts/bsd/cap/nfs" + +describe VagrantPlugins::HostBSD::Cap::NFS do + + include_context "unit" + + describe ".nfs_export" do + let(:environment) { double("environment", host: host) } + let(:host) { double("host") } + let(:ui) { double("ui") } + let(:id) { "UUID" } + let(:ips) { [] } + let(:folders) { {} } + + before do + allow(host).to receive(:capability).and_return("") + allow(Vagrant::Util::TemplateRenderer).to receive(:render).and_return("") + allow(described_class).to receive(:sleep) + allow(described_class).to receive(:nfs_cleanup) + allow(described_class).to receive(:system) + allow(File).to receive(:writable?).with("/etc/exports") + allow(ui).to receive(:info) + end + + it "should execute successfully when no folders are defined" do + expect { described_class.nfs_export(environment, ui, id, ips, folders) }. + not_to raise_error + end + + context "with single folder defined" do + let(:folders) { + {"/vagrant" => { + type: :nfs, guestpath: "/vagrant", hostpath: "/Users/vagrant/paths", disabled: false}} + } + + it "should execute successfully" do + expect { described_class.nfs_export(environment, ui, id, ips, folders) }. + not_to raise_error + end + + it "should resolve the host path" do + expect(host).to receive(:capability).with(:resolve_host_path, folders["/vagrant"][:hostpath]).and_return("") + described_class.nfs_export(environment, ui, id, ips, folders) + end + end + end +end diff --git a/test/unit/plugins/hosts/bsd/cap/path_test.rb b/test/unit/plugins/hosts/bsd/cap/path_test.rb new file mode 100644 index 000000000..ff865f30f --- /dev/null +++ b/test/unit/plugins/hosts/bsd/cap/path_test.rb @@ -0,0 +1,13 @@ +require_relative "../../../../base" +require_relative "../../../../../../plugins/hosts/bsd/cap/path" + +describe VagrantPlugins::HostBSD::Cap::Path do + describe ".resolve_host_path" do + let(:env) { double("environment") } + let(:path) { double("path") } + + it "should return the path object provided" do + expect(described_class.resolve_host_path(env, path)).to eq(path) + end + end +end diff --git a/test/unit/plugins/hosts/darwin/cap/path_test.rb b/test/unit/plugins/hosts/darwin/cap/path_test.rb new file mode 100644 index 000000000..f7e799e6c --- /dev/null +++ b/test/unit/plugins/hosts/darwin/cap/path_test.rb @@ -0,0 +1,89 @@ +require_relative "../../../../base" +require_relative "../../../../../../plugins/hosts/darwin/cap/path" + +describe VagrantPlugins::HostDarwin::Cap::Path do + describe ".resolve_host_path" do + let(:env) { double("environment") } + let(:path) { "/test/vagrant/path" } + let(:firmlink_map) { {} } + + before { allow(described_class).to receive(:firmlink_map).and_return(firmlink_map) } + + it "should not change the path when no firmlinks are defined" do + expect(described_class.resolve_host_path(env, path)).to eq(path) + end + + context "when firmlink map contains non-matching values" do + let(:firmlink_map) { {"/users" => "users", "/system" => "system"} } + + it "should not change the path" do + expect(described_class.resolve_host_path(env, path)).to eq(path) + end + end + + context "when firmlink map contains matching value" do + let(:firmlink_map) { {"/users" => "users", "/test" => "test"} } + + it "should update the path" do + expect(described_class.resolve_host_path(env, path)).not_to eq(path) + end + + it "should prefix the path with the defined data path" do + expect(described_class.resolve_host_path(env, path)).to start_with(described_class.const_get(:FIRMLINK_DATA_PATH)) + end + end + + context "when firmlink map match points to different named target" do + let(:firmlink_map) { {"/users" => "users", "/test" => "other"} } + + it "should update the path" do + expect(described_class.resolve_host_path(env, path)).not_to eq(path) + end + + it "should prefix the path with the defined data path" do + expect(described_class.resolve_host_path(env, path)).to start_with(described_class.const_get(:FIRMLINK_DATA_PATH)) + end + + it "should include the updated path name" do + expect(described_class.resolve_host_path(env, path)).to include("other") + end + end + end + + describe ".firmlink_map" do + before { described_class.reset! } + + context "when firmlink definition file does not exist" do + before { expect(File).to receive(:exist?).with(described_class.const_get(:FIRMLINK_DEFS)).and_return(false) } + + it "should return an empty hash" do + expect(described_class.firmlink_map).to eq({}) + end + end + + context "when firmlink definition file exists with values" do + before do + expect(File).to receive(:exist?).with(described_class.const_get(:FIRMLINK_DEFS)).and_return(true) + expect(File).to receive(:readlines).with.(described_class.const_get(:FIRMLINK_DEFS)). + and_return(["/System\tSystem\n", "/Users\tUsers\n", "/Library/Something\tLibrary/Somethingelse"]) + + it "should generate a non-empty hash" do + expect(described_class.firmlink_map).not_to be_empty + end + + it "should properly create entries" do + result = described_class.firmlink_map + expect(result["/System"]).to eq("System") + expect(result["/Users"]).to eq("Users") + expect(result["/Library/Something"]).to eq("Library/Somethingelse") + end + + it "should only load values once" do + result = describe_class.firmlink_app + expect(File).not_to receive(:readlines) + result = describe_class.firmlink_app + end + end + end + end +end