From b84505578c6e7fe587cf2eadd04a3a3aee2bcd70 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Jan 2014 13:45:44 -0800 Subject: [PATCH 1/5] add listen as a dep --- vagrant.gemspec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vagrant.gemspec b/vagrant.gemspec index 1b1bdc318..c0118db39 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -18,9 +18,12 @@ Gem::Specification.new do |s| s.add_dependency "childprocess", "~> 0.3.7" s.add_dependency "erubis", "~> 2.7.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 "net-ssh", ">= 2.6.6", "< 2.8.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 "contest", ">= 0.1.2" From fe2844ca5979a6bd5967b65e2c1a590e59e0d63b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Jan 2014 21:43:31 -0800 Subject: [PATCH 2/5] synced_folders/rsync: rsync-auto command --- .../rsync/command/rsync_auto.rb | 92 +++++++++++++++++++ plugins/synced_folders/rsync/plugin.rb | 5 + 2 files changed, 97 insertions(+) create mode 100644 plugins/synced_folders/rsync/command/rsync_auto.rb diff --git a/plugins/synced_folders/rsync/command/rsync_auto.rb b/plugins/synced_folders/rsync/command/rsync_auto.rb new file mode 100644 index 000000000..b0cbfb6ae --- /dev/null +++ b/plugins/synced_folders/rsync/command/rsync_auto.rb @@ -0,0 +1,92 @@ +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] << { + 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 + 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 diff --git a/plugins/synced_folders/rsync/plugin.rb b/plugins/synced_folders/rsync/plugin.rb index a39bccc68..d4d144613 100644 --- a/plugins/synced_folders/rsync/plugin.rb +++ b/plugins/synced_folders/rsync/plugin.rb @@ -14,6 +14,11 @@ module VagrantPlugins Command::Rsync end + command("rsync-auto", primary: false) do + require_relative "command/rsync_auto" + Command::RsyncAuto + end + synced_folder("rsync", 5) do require_relative "synced_folder" SyncedFolder From 04f18548405c2312618dbc9eaf844b25091d6140 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Jan 2014 22:01:09 -0800 Subject: [PATCH 3/5] synced_folders/rsync: unit tests for the callback, fix bug --- .../rsync/command/rsync_auto.rb | 4 + .../rsync/command/rsync_auto_test.rb | 99 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 test/unit/plugins/synced_folders/rsync/command/rsync_auto_test.rb diff --git a/plugins/synced_folders/rsync/command/rsync_auto.rb b/plugins/synced_folders/rsync/command/rsync_auto.rb index b0cbfb6ae..42d9e4e55 100644 --- a/plugins/synced_folders/rsync/command/rsync_auto.rb +++ b/plugins/synced_folders/rsync/command/rsync_auto.rb @@ -72,6 +72,10 @@ module VagrantPlugins 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 diff --git a/test/unit/plugins/synced_folders/rsync/command/rsync_auto_test.rb b/test/unit/plugins/synced_folders/rsync/command/rsync_auto_test.rb new file mode 100644 index 000000000..7da7cc9a8 --- /dev/null +++ b/test/unit/plugins/synced_folders/rsync/command/rsync_auto_test.rb @@ -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 From 720cb4378b054a1976faa3ae1a96e030b5ca8247 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Jan 2014 22:19:18 -0800 Subject: [PATCH 4/5] website/docs: rsync-auto --- website/docs/source/v2/cli/non-primary.html.md | 1 + website/docs/source/v2/cli/rsync-auto.html.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 website/docs/source/v2/cli/rsync-auto.html.md diff --git a/website/docs/source/v2/cli/non-primary.html.md b/website/docs/source/v2/cli/non-primary.html.md index 0dd43539b..2138bd24a 100644 --- a/website/docs/source/v2/cli/non-primary.html.md +++ b/website/docs/source/v2/cli/non-primary.html.md @@ -22,3 +22,4 @@ The list of non-primary commands is below. Click on any command to learn more about it. * [rsync](/v2/cli/rsync.html) +* [rsync-auto](/v2/cli/rsync-auto.html) diff --git a/website/docs/source/v2/cli/rsync-auto.html.md b/website/docs/source/v2/cli/rsync-auto.html.md new file mode 100644 index 000000000..a19e3efd8 --- /dev/null +++ b/website/docs/source/v2/cli/rsync-auto.html.md @@ -0,0 +1,16 @@ +--- +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. From 526231812ff5c97dc17b17aed2c77bb49eea15fe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 13 Jan 2014 22:29:10 -0800 Subject: [PATCH 5/5] website/docs: better docs --- .../rsync/command/rsync_auto.rb | 1 + website/docs/source/v2/cli/rsync-auto.html.md | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/plugins/synced_folders/rsync/command/rsync_auto.rb b/plugins/synced_folders/rsync/command/rsync_auto.rb index 42d9e4e55..f7a6b1ae7 100644 --- a/plugins/synced_folders/rsync/command/rsync_auto.rb +++ b/plugins/synced_folders/rsync/command/rsync_auto.rb @@ -40,6 +40,7 @@ module VagrantPlugins hostpath = File.expand_path(hostpath, machine.env.root_path) paths[hostpath] ||= [] paths[hostpath] << { + id: id, machine: machine, opts: folder_opts, } diff --git a/website/docs/source/v2/cli/rsync-auto.html.md b/website/docs/source/v2/cli/rsync-auto.html.md index a19e3efd8..9eec9dfc0 100644 --- a/website/docs/source/v2/cli/rsync-auto.html.md +++ b/website/docs/source/v2/cli/rsync-auto.html.md @@ -14,3 +14,27 @@ 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.