Merge pull request #2803 from mitchellh/f-rsync
rsync Synced Folder Type This pull requests introduces the `rsync` synced folder type into Vagrant core. This synced folder will do a one-time one-directional sync from the host machine to the guest machine using rsync. This is useful in situations where NFS or native shared folders can't be setup, such as in AWS. Pretty easy to use: `config.vm.synced_folder ".", "/vagrant", type: "rsync"` No additional configuration necessary, though additional options are available. In the future, I'd like to add the ability to force a re-sync, as well as to watch for file changes and sync. For now, this is a basic one-time sync. Having this in core allows remote providers such as OpenStack, AWS, RackSpace, etc. to stop hand-rolling the rsync synced folder mechanism and to rely on the core providing it. And a shout out to @phinze because this thing is fully unit tested. Fixes #1926
This commit is contained in:
commit
84108bee9a
|
@ -71,7 +71,7 @@ module Vagrant
|
|||
raise "Internal error. Report this as a bug. Invalid: #{data[:type]}"
|
||||
end
|
||||
|
||||
if !impl_class[0].new.usable?(machine)
|
||||
if !impl_class[0].new.usable?(machine, true)
|
||||
# Verify that explicitly defined shared folder types are
|
||||
# actually usable.
|
||||
raise Errors::SyncedFolderUnusable, type: data[:type].to_s
|
||||
|
|
|
@ -420,6 +420,14 @@ module Vagrant
|
|||
error_key(:plugin_state_file_not_parsable)
|
||||
end
|
||||
|
||||
class RSyncError < VagrantError
|
||||
error_key(:rsync_error)
|
||||
end
|
||||
|
||||
class RSyncNotFound < VagrantError
|
||||
error_key(:rsync_not_found)
|
||||
end
|
||||
|
||||
class SCPPermissionDenied < VagrantError
|
||||
error_key(:scp_permission_denied)
|
||||
end
|
||||
|
|
|
@ -7,8 +7,11 @@ module Vagrant
|
|||
# if this implementation can be used for this machine. This should
|
||||
# return true or false.
|
||||
#
|
||||
# @param [Machine] machine
|
||||
# @param [Boolean] raise_error If true, should raise an exception
|
||||
# if it isn't usable.
|
||||
# @return [Boolean]
|
||||
def usable?(machine)
|
||||
def usable?(machine, raise_error=false)
|
||||
end
|
||||
|
||||
# This is called before the machine is booted, allowing the
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
module VagrantPlugins
|
||||
module GuestLinux
|
||||
module Cap
|
||||
class RSync
|
||||
def self.rsync_pre(machine, folder_opts)
|
||||
username = machine.ssh_info[:username]
|
||||
|
||||
machine.communicate.tap do |comm|
|
||||
comm.sudo("mkdir -p '#{folder_opts[:guestpath]}'")
|
||||
comm.sudo("chown -R #{username} '#{folder_opts[:guestpath]}'")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,6 +45,12 @@ module VagrantPlugins
|
|||
require_relative "cap/read_ip_address"
|
||||
Cap::ReadIPAddress
|
||||
end
|
||||
|
||||
# RSync synced folders
|
||||
guest_capability("linux", "rsync_pre") do
|
||||
require_relative "cap/rsync"
|
||||
Cap::RSync
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,7 +24,7 @@ module VagrantPlugins
|
|||
@logger = Log4r::Logger.new("vagrant::synced_folders::nfs")
|
||||
end
|
||||
|
||||
def usable?(machine)
|
||||
def usable?(machine, raise_error=false)
|
||||
# NFS is always available
|
||||
true
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,95 @@
|
|||
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)
|
||||
ssh_info = machine.ssh_info
|
||||
|
||||
folders.each do |id, folder_opts|
|
||||
rsync_single(machine, ssh_info, folder_opts)
|
||||
end
|
||||
end
|
||||
|
||||
# rsync_single rsync's a single folder with the given options.
|
||||
def rsync_single(machine, ssh_info, 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.
|
||||
excludes = ['.vagrant/']
|
||||
excludes += Array(opts[:exclude]).map(&:to_s) if opts[:exclude]
|
||||
excludes.uniq!
|
||||
|
||||
# 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] = machine.env.root_path.to_s
|
||||
|
||||
machine.ui.info(I18n.t(
|
||||
"vagrant.rsync_folder", guestpath: guestpath, hostpath: hostpath))
|
||||
if excludes.length > 1
|
||||
machine.ui.info(I18n.t(
|
||||
"vagrant.rsync_folder_excludes", excludes: excludes.inspect))
|
||||
end
|
||||
|
||||
# If we have tasks to do before rsyncing, do those.
|
||||
if machine.guest.capability?(:rsync_pre)
|
||||
machine.guest.capability(:rsync_pre, opts)
|
||||
end
|
||||
|
||||
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
|
|
@ -80,6 +80,9 @@ en:
|
|||
%{names}
|
||||
provisioner_cleanup: |-
|
||||
Running cleanup tasks for '%{name}' provisioner...
|
||||
rsync_folder: |-
|
||||
Rsyncing folder: %{hostpath} => %{guestpath}
|
||||
rsync_folder_excludes: " - Exclude: %{excludes}"
|
||||
ssh_exec_password: |-
|
||||
The machine you're attempting to SSH into is configured to use
|
||||
password-based authentication. Vagrant can't script entering the
|
||||
|
@ -525,6 +528,17 @@ en:
|
|||
provisioner_flag_invalid: |-
|
||||
'%{name}' is not a known provisioner. Please specify a valid
|
||||
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: |-
|
||||
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
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
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(:guest) { double("guest") }
|
||||
let(:host) { double("host") }
|
||||
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
|
||||
|
||||
before do
|
||||
machine.env.stub(host: host)
|
||||
machine.stub(guest: guest)
|
||||
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(machine, ssh_info, 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(:ui) { machine.ui }
|
||||
|
||||
before do
|
||||
Vagrant::Util::Subprocess.stub(execute: result)
|
||||
|
||||
guest.stub(capability?: false)
|
||||
end
|
||||
|
||||
it "doesn't raise an error if it succeeds" do
|
||||
subject.rsync_single(machine, ssh_info, 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(machine, ssh_info, 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(machine.env.root_path.to_s)
|
||||
end
|
||||
|
||||
subject.rsync_single(machine, ssh_info, opts)
|
||||
end
|
||||
|
||||
it "executes the rsync_pre capability first if it exists" do
|
||||
guest.should_receive(:capability?).with(:rsync_pre).and_return(true)
|
||||
guest.should_receive(:capability).with(:rsync_pre, opts).ordered
|
||||
Vagrant::Util::Subprocess.should_receive(:execute).ordered.and_return(result)
|
||||
|
||||
subject.rsync_single(machine, ssh_info, opts)
|
||||
end
|
||||
|
||||
context "excluding files" do
|
||||
it "excludes files if given as a string" do
|
||||
opts[:exclude] = "foo"
|
||||
|
||||
Vagrant::Util::Subprocess.should_receive(:execute).with do |*args|
|
||||
index = args.find_index("foo")
|
||||
expect(index).to be > 0
|
||||
expect(args[index-1]).to eql("--exclude")
|
||||
end
|
||||
|
||||
subject.rsync_single(machine, ssh_info, opts)
|
||||
end
|
||||
|
||||
it "excludes multiple files" do
|
||||
opts[:exclude] = ["foo", "bar"]
|
||||
|
||||
Vagrant::Util::Subprocess.should_receive(:execute).with do |*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")
|
||||
end
|
||||
|
||||
subject.rsync_single(machine, ssh_info, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,7 +6,8 @@ shared_context "synced folder actions" do
|
|||
name
|
||||
end
|
||||
|
||||
define_method(:usable?) do |machine|
|
||||
define_method(:usable?) do |machine, raise_error=false|
|
||||
raise "#{name}: usable" if raise_error && !usable
|
||||
usable
|
||||
end
|
||||
end
|
||||
|
|
|
@ -173,6 +173,7 @@
|
|||
<ul class="sub unstyled">
|
||||
<li<%= sidebar_current("syncedfolder-basic") %>><a href="/v2/synced-folders/basic_usage.html">Basic Usage</a></li>
|
||||
<li<%= sidebar_current("syncedfolder-nfs") %>><a href="/v2/synced-folders/nfs.html">NFS</a></li>
|
||||
<li<%= sidebar_current("syncedfolder-rsync") %>><a href="/v2/synced-folders/rsync.html">RSync</a></li>
|
||||
<li<%= sidebar_current("syncedfolder-virtualbox") %>><a href="/v2/synced-folders/virtualbox.html">VirtualBox</a></li>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
page_title: "RSync - Synced Folders"
|
||||
sidebar_current: "syncedfolder-rsync"
|
||||
---
|
||||
|
||||
# RSync
|
||||
|
||||
**Synced folder type:** `rsync`
|
||||
|
||||
Vagrant can use [rsync](http://en.wikipedia.org/wiki/Rsync) as a mechanism
|
||||
to sync a folder to the guest machine. This synced folder type is useful
|
||||
primarily in situations where other synced folder mechanisms are not available,
|
||||
such as when NFS or VirtualBox shared folders aren't available in the guest
|
||||
machine.
|
||||
|
||||
The rsync synced folder does a one-time one-way sync from the machine running
|
||||
to the machine being started by Vagrant.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use the rsync synced folder type, the machine running Vagrant must have
|
||||
`rsync` (or `rsync.exe`) on the path. This executable is expected to behave
|
||||
like the standard rsync tool.
|
||||
|
||||
## Options
|
||||
|
||||
The rsync synced folder type accepts the following options:
|
||||
|
||||
* `rsync__exclude` (string or array of strings) - A list of files or directories
|
||||
to exclude from the sync. The values can be any acceptable rsync exclude
|
||||
pattern. By default, the ".vagrant/" directory is excluded. We recommend
|
||||
excluding revision control directories such as ".git/" as well.
|
||||
|
||||
## Example
|
||||
|
||||
The following is an example of using RSync to sync a folder:
|
||||
|
||||
<pre class="prettyprint">
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.synced_folder ".", "/vagrant", type: "rsync",
|
||||
rsync__exclude: ".git/"
|
||||
end
|
||||
</pre>
|
||||
|
||||
## Re-Syncing
|
||||
|
||||
The rsync sync is done only during a `vagrant up` or `vagrant reload`. It
|
||||
is not currently possible to force a re-sync in any way other than reloading.
|
||||
|
||||
We plan on exposing a command to force a sync in a future version of Vagrant.
|
Loading…
Reference in New Issue