Merge pull request #2820 from mitchellh/f-rsync-listen

`rsync-auto` command
This commit is contained in:
Mitchell Hashimoto 2014-01-14 07:38:38 -08:00
commit 8d771de9ba
6 changed files with 245 additions and 0 deletions

View File

@ -0,0 +1,97 @@
require "log4r"
require 'optparse'
require "listen"
require "vagrant/action/builtin/mixin_synced_folders"
require_relative "../helper"
module VagrantPlugins
module SyncedFolderRSync
module Command
class RsyncAuto < Vagrant.plugin("2", :command)
include Vagrant::Action::Builtin::MixinSyncedFolders
def self.synopsis
"syncs rsync synced folders automatically when files change"
end
def execute
@logger = Log4r::Logger.new("vagrant::commands::rsync-auto")
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant rsync-auto [vm-name]"
o.separator ""
end
# Parse the options and return if we don't have any target.
argv = parse_options(opts)
return if !argv
# Build up the paths that we need to listen to.
paths = {}
with_target_vms(argv) do |machine|
folders = synced_folders(machine)[:rsync]
next if !folders || folders.empty?
folders.each do |id, folder_opts|
hostpath = folder_opts[:hostpath]
hostpath = File.expand_path(hostpath, machine.env.root_path)
paths[hostpath] ||= []
paths[hostpath] << {
id: id,
machine: machine,
opts: folder_opts,
}
end
end
@logger.info("Listening to paths: #{paths.keys.sort.inspect}")
@logger.info("Listening via: #{Listen::Adapter.select.inspect}")
callback = method(:callback).to_proc.curry[paths]
listener = Listen.to(*paths.keys, &callback)
listener.start
listener.thread.join
0
end
# This is the callback that is called when any changes happen
def callback(paths, modified, added, removed)
@logger.debug("File change callback called!")
@logger.debug(" - Modified: #{modified.inspect}")
@logger.debug(" - Added: #{added.inspect}")
@logger.debug(" - Removed: #{removed.inspect}")
tosync = []
paths.each do |hostpath, folders|
# Find out if this path should be synced
found = catch(:done) do
[modified, added, removed].each do |changed|
changed.each do |listenpath|
throw :done, true if listenpath.start_with?(hostpath)
end
end
# Make sure to return false if all else fails so that we
# don't sync to this machine.
false
end
# If it should be synced, store it for later
tosync << folders if found
end
# Sync all the folders that need to be synced
tosync.each do |folders|
folders.each do |opts|
ssh_info = opts[:machine].ssh_info
RsyncHelper.rsync_single(opts[:machine], ssh_info, opts[:opts])
end
end
end
end
end
end
end

View File

@ -14,6 +14,11 @@ module VagrantPlugins
Command::Rsync Command::Rsync
end end
command("rsync-auto", primary: false) do
require_relative "command/rsync_auto"
Command::RsyncAuto
end
synced_folder("rsync", 5) do synced_folder("rsync", 5) do
require_relative "synced_folder" require_relative "synced_folder"
SyncedFolder SyncedFolder

View File

@ -0,0 +1,99 @@
require_relative "../../../../base"
require Vagrant.source_root.join("plugins/synced_folders/rsync/command/rsync_auto")
describe VagrantPlugins::SyncedFolderRSync::Command::RsyncAuto do
include_context "unit"
let(:argv) { [] }
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(:synced_folders) { {} }
let(:helper_class) { VagrantPlugins::SyncedFolderRSync::RsyncHelper }
subject do
described_class.new(argv, iso_env).tap do |s|
s.stub(synced_folders: synced_folders)
end
end
describe "#callback" do
let(:paths) { {} }
let(:ssh_info) {{}}
def machine_stub(name)
double(name).tap do |m|
m.stub(ssh_info: ssh_info)
end
end
it "syncs modified folders to the proper path" do
paths["/foo"] = [
{ machine: machine_stub("m1"), opts: double("opts_m1") },
{ machine: machine_stub("m2"), opts: double("opts_m2") },
]
paths["/bar"] = [
{ machine: machine_stub("m3"), opts: double("opts_m3") },
]
paths["/foo"].each do |data|
helper_class.should_receive(:rsync_single).
with(data[:machine], data[:machine].ssh_info, data[:opts]).
once
end
m = ["/foo/bar"]
a = []
r = []
subject.callback(paths, m, a, r)
end
it "syncs added folders to the proper path" do
paths["/foo"] = [
{ machine: machine_stub("m1"), opts: double("opts_m1") },
{ machine: machine_stub("m2"), opts: double("opts_m2") },
]
paths["/bar"] = [
{ machine: machine_stub("m3"), opts: double("opts_m3") },
]
paths["/foo"].each do |data|
helper_class.should_receive(:rsync_single).
with(data[:machine], data[:machine].ssh_info, data[:opts]).
once
end
m = []
a = ["/foo/bar"]
r = []
subject.callback(paths, m, a, r)
end
it "syncs removed folders to the proper path" do
paths["/foo"] = [
{ machine: machine_stub("m1"), opts: double("opts_m1") },
{ machine: machine_stub("m2"), opts: double("opts_m2") },
]
paths["/bar"] = [
{ machine: machine_stub("m3"), opts: double("opts_m3") },
]
paths["/foo"].each do |data|
helper_class.should_receive(:rsync_single).
with(data[:machine], data[:machine].ssh_info, data[:opts]).
once
end
m = []
a = []
r = ["/foo/bar"]
subject.callback(paths, m, a, r)
end
end
end

View File

@ -18,9 +18,12 @@ Gem::Specification.new do |s|
s.add_dependency "childprocess", "~> 0.3.7" s.add_dependency "childprocess", "~> 0.3.7"
s.add_dependency "erubis", "~> 2.7.0" s.add_dependency "erubis", "~> 2.7.0"
s.add_dependency "i18n", "~> 0.6.0" s.add_dependency "i18n", "~> 0.6.0"
s.add_dependency "listen", "~> 2.4.0"
s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11" s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11"
s.add_dependency "net-ssh", ">= 2.6.6", "< 2.8.0" s.add_dependency "net-ssh", ">= 2.6.6", "< 2.8.0"
s.add_dependency "net-scp", "~> 1.1.0" s.add_dependency "net-scp", "~> 1.1.0"
s.add_dependency "rb-kqueue", "~> 0.2.0"
s.add_dependency "wdm", "~> 0.1.0"
s.add_development_dependency "rake" s.add_development_dependency "rake"
s.add_development_dependency "contest", ">= 0.1.2" s.add_development_dependency "contest", ">= 0.1.2"

View File

@ -22,3 +22,4 @@ The list of non-primary commands is below. Click on any command to learn
more about it. more about it.
* [rsync](/v2/cli/rsync.html) * [rsync](/v2/cli/rsync.html)
* [rsync-auto](/v2/cli/rsync-auto.html)

View File

@ -0,0 +1,40 @@
---
page_title: "vagrant rsync-auto - Command-Line Interface"
sidebar_current: "cli-rsyncauto"
---
# rsync-auto
**Command: `vagrant rsync-auto`**
This command watches all local directories of anj
[rsync synced folders](/v2/synced-folders/rsync.html) and automatically
initiates an rsync transfer when changes are detected. This command does
not exit until an interrupt is received.
The change detection is optimized to use platform-specific APIs to listen
for filesystem changes, and does not simply poll the directory.
## Machine State Changes
The `rsync-auto` command does not currently handle machine state changes
gracefully. For example, if you start the `rsync-auto` command, then
halt the guest machine, then make changes to some files, then boot it
back up, `rsync-auto` will not attempt to resync.
To ensure that the command works properly, you should start `rsync-auto`
only when the machine is running, and shut it down before any machine
state changes.
You can always force a resync with the [rsync](/v2/cli/rsync.html) command.
## Vagrantfile Changes
If you change or move your Vagrantfile, the `rsync-auto` command will have
to be restarted. For example, if you add synced folders to the Vagrantfile,
or move the directory that contains the Vagrantfile, the `rsync-auto`
command will either not pick up the changes or may begin experiencing
strange behavior.
Before making any such changes, it is recommended that you turn off
`rsync-auto`, then restart it afterwards.