synced_folders/rsync: Initial commit working

This commit is contained in:
Mitchell Hashimoto 2014-01-10 17:40:29 -08:00
parent 213000fd3d
commit 38fbbb6c56
9 changed files with 233 additions and 4 deletions

View File

@ -71,7 +71,7 @@ module Vagrant
raise "Internal error. Report this as a bug. Invalid: #{data[:type]}" raise "Internal error. Report this as a bug. Invalid: #{data[:type]}"
end end
if !impl_class[0].new.usable?(machine) if !impl_class[0].new.usable?(machine, true)
# Verify that explicitly defined shared folder types are # Verify that explicitly defined shared folder types are
# actually usable. # actually usable.
raise Errors::SyncedFolderUnusable, type: data[:type].to_s raise Errors::SyncedFolderUnusable, type: data[:type].to_s

View File

@ -420,6 +420,14 @@ module Vagrant
error_key(:plugin_state_file_not_parsable) error_key(:plugin_state_file_not_parsable)
end end
class RSyncError < VagrantError
error_key(:rsync_error)
end
class RSyncNotFound < VagrantError
error_key(:rsync_not_found)
end
class SCPPermissionDenied < VagrantError class SCPPermissionDenied < VagrantError
error_key(:scp_permission_denied) error_key(:scp_permission_denied)
end end

View File

@ -7,8 +7,11 @@ module Vagrant
# if this implementation can be used for this machine. This should # if this implementation can be used for this machine. This should
# return true or false. # return true or false.
# #
# @param [Machine] machine
# @param [Boolean] raise_error If true, should raise an exception
# if it isn't usable.
# @return [Boolean] # @return [Boolean]
def usable?(machine) def usable?(machine, raise_error=false)
end end
# This is called before the machine is booted, allowing the # This is called before the machine is booted, allowing the

View File

@ -24,7 +24,7 @@ module VagrantPlugins
@logger = Log4r::Logger.new("vagrant::synced_folders::nfs") @logger = Log4r::Logger.new("vagrant::synced_folders::nfs")
end end
def usable?(machine) def usable?(machine, raise_error=false)
# NFS is always available # NFS is always available
true true
end end

View File

@ -0,0 +1,18 @@
require "vagrant"
module VagrantPlugins
module SyncedFolderRSync
# This plugin implements synced folders via rsync.
class Plugin < Vagrant.plugin("2")
name "RSync synced folders"
description <<-EOF
The Rsync synced folder plugin will sync folders via rsync.
EOF
synced_folder("rsync", 5) do
require_relative "synced_folder"
SyncedFolder
end
end
end
end

View File

@ -0,0 +1,86 @@
require "log4r"
require "vagrant/util/subprocess"
require "vagrant/util/which"
module VagrantPlugins
module SyncedFolderRSync
class SyncedFolder < Vagrant.plugin("2", :synced_folder)
include Vagrant::Util
def initialize(*args)
super
@logger = Log4r::Logger.new("vagrant::synced_folders::rsync")
end
def usable?(machine, raise_error=false)
rsync_path = Which.which("rsync")
return true if rsync_path
return false if !raise_error
raise Vagrant::Errors::RSyncNotFound
end
def prepare(machine, folders, opts)
# Nothing is necessary to do before VM boot.
end
def enable(machine, folders, opts)
rootdir = machine.env.root_path.to_s
ssh_info = machine.ssh_info
folders.each do |id, folder_opts|
rsync_single(ssh_info, rootdir, machine.ui, folder_opts)
end
end
# rsync_single rsync's a single folder with the given options.
def rsync_single(ssh_info, rootdir, ui, opts)
# Folder info
guestpath = opts[:guestpath]
hostpath = opts[:hostpath]
# Connection information
username = ssh_info[:username]
host = ssh_info[:host]
rsh = [
"ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no",
ssh_info[:private_key_path].map { |p| "-i '#{p}'" },
].flatten.join(" ")
# Exclude some files by default, and any that might be configured
# by the user.
# TODO(mitchellh): allow the user to configure it
excludes = ['.vagrant/']
# Build up the actual command to execute
command = [
"rsync",
"--verbose",
"--archive",
"-z",
excludes.map { |e| ["--exclude", e] },
"-e", rsh,
hostpath,
"#{username}@#{host}:#{guestpath}"
].flatten
command_opts = {}
# The working directory should be the root path
command_opts[:workdir] = rootdir
ui.info(I18n.t(
"vagrant.rsync_folder", guestpath: guestpath, hostpath: hostpath))
r = Vagrant::Util::Subprocess.execute(*(command + [command_opts]))
if r.exit_code != 0
raise Vagrant::Errors::RSyncError,
command: command.join(" "),
guestpath: guestpath,
hostpath: hostpath,
stderr: r.stderr
end
end
end
end
end

View File

@ -80,6 +80,8 @@ en:
%{names} %{names}
provisioner_cleanup: |- provisioner_cleanup: |-
Running cleanup tasks for '%{name}' provisioner... Running cleanup tasks for '%{name}' provisioner...
rsync_folder: |-
Rsyncing folder: %{hostpath} => %{guestpath}
ssh_exec_password: |- ssh_exec_password: |-
The machine you're attempting to SSH into is configured to use The machine you're attempting to SSH into is configured to use
password-based authentication. Vagrant can't script entering the password-based authentication. Vagrant can't script entering the
@ -525,6 +527,17 @@ en:
provisioner_flag_invalid: |- provisioner_flag_invalid: |-
'%{name}' is not a known provisioner. Please specify a valid '%{name}' is not a known provisioner. Please specify a valid
provisioner. provisioner.
rsync_error: |-
There was an error when attempting to rsync a synced folder.
Please inspect the error message below for more info.
Host path: %{hostpath}
Guest path: %{guestpath}
Command: %{command}
Error: %{stderr}
rsync_not_found: |-
"rsync" could not be found on your PATH. Make sure that rsync
is properly installed on your system and available on the PATH.
scp_permission_denied: |- scp_permission_denied: |-
Failed to upload a file to the guest VM via SCP due to a permissions Failed to upload a file to the guest VM via SCP due to a permissions
error. This is normally because the user running Vagrant doesn't have error. This is normally because the user running Vagrant doesn't have

View File

@ -0,0 +1,100 @@
require_relative "../../../base"
require Vagrant.source_root.join("plugins/synced_folders/rsync/synced_folder")
describe VagrantPlugins::SyncedFolderRSync::SyncedFolder 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(:host) { double("host") }
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
before do
machine.env.stub(host: host)
end
describe "#usable?" do
it "is usable if rsync can be found" do
Vagrant::Util::Which.should_receive(:which).with("rsync").and_return(true)
expect(subject.usable?(machine)).to be_true
end
it "is not usable if rsync cant be found" do
Vagrant::Util::Which.should_receive(:which).with("rsync").and_return(false)
expect(subject.usable?(machine)).to be_false
end
it "raises an exception if asked to" do
Vagrant::Util::Which.should_receive(:which).with("rsync").and_return(false)
expect { subject.usable?(machine, true) }.
to raise_error(Vagrant::Errors::RSyncNotFound)
end
end
describe "#enable" do
let(:ssh_info) { Object.new }
before do
machine.stub(ssh_info: ssh_info)
end
it "rsyncs each folder" do
folders = [
[:one, {}],
[:two, {}],
]
folders.each do |_, opts|
subject.should_receive(:rsync_single).
with(ssh_info, machine.env.root_path.to_s, machine.ui, opts).
ordered
end
subject.enable(machine, folders, {})
end
end
describe "#rsync_single" do
let(:result) { Vagrant::Util::Subprocess::Result.new(0, "", "") }
let(:ssh_info) {{
private_key_path: [],
}}
let(:opts) { {} }
let(:root_path) { "foo" }
let(:ui) { machine.ui }
before do
Vagrant::Util::Subprocess.stub(execute: result)
end
it "doesn't raise an error if it succeeds" do
subject.rsync_single(ssh_info, root_path, ui, opts)
end
it "raises an error if the exit code is non-zero" do
Vagrant::Util::Subprocess.stub(
execute: Vagrant::Util::Subprocess::Result.new(1, "", ""))
expect {subject.rsync_single(ssh_info, root_path, ui, opts) }.
to raise_error(Vagrant::Errors::RSyncError)
end
it "executes within the root path" do
Vagrant::Util::Subprocess.should_receive(:execute).with do |*args|
expect(args.last).to be_kind_of(Hash)
opts = args.last
expect(opts[:workdir]).to eql(root_path)
end
subject.rsync_single(ssh_info, root_path, ui, opts)
end
end
end

View File

@ -6,7 +6,8 @@ shared_context "synced folder actions" do
name name
end end
define_method(:usable?) do |machine| define_method(:usable?) do |machine, raise_error=false|
raise "#{name}: usable" if raise_error && !usable
usable usable
end end
end end