Brian Cain 4440056fff (#8770) rsync-auto relative dirs from vagrantfile
Prior to this commit, rsync-auto would not properly rsync realtive dirs
outside the cwd if defined in the Vagrantfile. This commit updates that
to ensure that the command looks at the Vagrant config to ensure that
folder was intended to be rsync'd to the machine even if outside the
current working dir.
2017-07-14 09:26:10 -07:00

232 lines
8.2 KiB
Ruby

require "log4r"
require 'optparse'
require "thread"
require "vagrant/action/builtin/mixin_synced_folders"
require "vagrant/util/busy"
require "vagrant/util/platform"
require_relative "../helper"
# This is to avoid a bug in nio 1.0.0. Remove around nio 1.0.1
if Vagrant::Util::Platform.windows?
ENV["NIO4R_PURE"] = "1"
end
require "listen"
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")
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant rsync-auto [vm-name]"
o.separator ""
o.separator "Options:"
o.separator ""
o.on("--[no-]poll", "Force polling filesystem (slow)") do |poll|
options[:poll] = poll
end
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 = {}
ignores = []
with_target_vms(argv) do |machine|
next if machine.state.id == :not_created
cwd = machine.env.cwd.to_s
if machine.provider.capability?(:proxy_machine)
proxy = machine.provider.capability(:proxy_machine)
if proxy
machine.ui.warn(I18n.t(
"vagrant.rsync_proxy_machine",
name: machine.name.to_s,
provider: machine.provider_name.to_s))
machine = proxy
end
end
cached = synced_folders(machine, cached: true)
fresh = synced_folders(machine)
diff = synced_folders_diff(cached, fresh)
if !diff[:added].empty?
machine.ui.warn(I18n.t("vagrant.rsync_auto_new_folders"))
end
folders = cached[:rsync]
next if !folders || folders.empty?
# NOTE: This check is required with boot2docker since all containers
# share the same virtual machine. This prevents rsync-auto from
# syncing all known containers with rsync to the boot2docker vm
# and only syncs the current working dirs folders.
sync_folders = {}
# Still sync existing synced folders from vagrantfile
config_synced_folders = machine.config.vm.synced_folders.values.map { |x| x[:hostpath] }
config_synced_folders.map! { |x| File.expand_path(x, machine.env.root_path) }
folders.each do |id, folder_opts|
if cwd != folder_opts[:hostpath] &&
!config_synced_folders.include?(folder_opts[:hostpath])
machine.ui.info(I18n.t("vagrant.rsync_auto_remove_folder",
folder: folder_opts[:hostpath]))
else
sync_folders[id] = folder_opts
end
end
folders = sync_folders
# Get the SSH info for this machine so we can do an initial
# sync to the VM.
ssh_info = machine.ssh_info
if ssh_info
machine.ui.info(I18n.t("vagrant.rsync_auto_initial"))
folders.each do |id, folder_opts|
RsyncHelper.rsync_single(machine, ssh_info, folder_opts)
end
end
folders.each do |id, folder_opts|
# If we marked this folder to not auto sync, then
# don't do it.
next if folder_opts.key?(:auto) && !folder_opts[:auto]
hostpath = folder_opts[:hostpath]
hostpath = File.expand_path(hostpath, machine.env.root_path)
paths[hostpath] ||= []
paths[hostpath] << {
id: id,
machine: machine,
opts: folder_opts,
}
if folder_opts[:exclude]
Array(folder_opts[:exclude]).each do |pattern|
ignores << RsyncHelper.exclude_to_regexp(hostpath, pattern.to_s)
end
end
end
end
# Exit immediately if there is nothing to watch
if paths.empty?
@env.ui.info(I18n.t("vagrant.rsync_auto_no_paths"))
return 1
end
# Output to the user what paths we'll be watching
paths.keys.sort.each do |path|
paths[path].each do |path_opts|
path_opts[:machine].ui.info(I18n.t(
"vagrant.rsync_auto_path",
path: path.to_s,
))
end
end
@logger.info("Listening to paths: #{paths.keys.sort.inspect}")
@logger.info("Ignoring #{ignores.length} paths:")
ignores.each do |ignore|
@logger.info(" -- #{ignore.to_s}")
end
@logger.info("Listening via: #{Listen::Adapter.select.inspect}")
callback = method(:callback).to_proc.curry[paths]
listopts = { ignore: ignores, force_polling: !!options[:poll] }
listener = Listen.to(*paths.keys, listopts, &callback)
# Create the callback that lets us know when we've been interrupted
queue = Queue.new
callback = lambda do
# This needs to execute in another thread because Thread
# synchronization can't happen in a trap context.
Thread.new { queue << true }
end
# Run the listener in a busy block so that we can cleanly
# exit once we receive an interrupt.
Vagrant::Util::Busy.busy(callback) do
listener.start
queue.pop
listener.stop if listener.state != :stopped
end
0
end
# This is the callback that is called when any changes happen
def callback(paths, modified, added, removed)
@logger.info("File change callback called!")
@logger.info(" - Modified: #{modified.inspect}")
@logger.info(" - Added: #{added.inspect}")
@logger.info(" - 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|
# Reload so we get the latest ID
opts[:machine].reload
if !opts[:machine].id || opts[:machine].id == ""
# Skip since we can't get SSH info without an ID
next
end
ssh_info = opts[:machine].ssh_info
begin
start = Time.now
RsyncHelper.rsync_single(opts[:machine], ssh_info, opts[:opts])
finish = Time.now
@logger.info("Time spent in rsync: #{finish-start} (in seconds)")
rescue Vagrant::Errors::MachineGuestNotReady
# Error communicating to the machine, probably a reload or
# halt is happening. Just notify the user but don't fail out.
opts[:machine].ui.error(I18n.t(
"vagrant.rsync_communicator_not_ready_callback"))
rescue Vagrant::Errors::RSyncError => e
# Error executing rsync, so show an error
opts[:machine].ui.error(I18n.t(
"vagrant.rsync_auto_rsync_error", message: e.to_s))
end
end
end
end
end
end
end
end