2014-01-15 19:30:32 +00:00
|
|
|
require "vagrant/util/platform"
|
2014-01-13 19:01:50 +00:00
|
|
|
require "vagrant/util/subprocess"
|
|
|
|
|
|
|
|
module VagrantPlugins
|
|
|
|
module SyncedFolderRSync
|
|
|
|
# This is a helper that abstracts out the functionality of rsyncing
|
|
|
|
# folders so that it can be called from anywhere.
|
|
|
|
class RsyncHelper
|
2014-03-13 02:40:18 +00:00
|
|
|
# This converts an rsync exclude pattern to a regular expression
|
|
|
|
# we can send to Listen.
|
|
|
|
def self.exclude_to_regexp(path, exclude)
|
|
|
|
start_anchor = false
|
|
|
|
|
|
|
|
if exclude.start_with?("/")
|
|
|
|
start_anchor = true
|
|
|
|
exclude = exclude[1..-1]
|
|
|
|
end
|
|
|
|
|
|
|
|
path = "#{path}/" if !path.end_with?("/")
|
|
|
|
regexp = "^#{Regexp.escape(path)}"
|
|
|
|
regexp += ".*" if !start_anchor
|
|
|
|
|
|
|
|
# This is REALLY ghetto, but its a start. We can improve and
|
|
|
|
# keep unit tests passing in the future.
|
|
|
|
exclude = exclude.gsub("**", "|||GLOBAL|||")
|
|
|
|
exclude = exclude.gsub("*", "|||PATH|||")
|
|
|
|
exclude = exclude.gsub("|||PATH|||", "[^/]*")
|
|
|
|
exclude = exclude.gsub("|||GLOBAL|||", ".*")
|
|
|
|
regexp += exclude
|
|
|
|
|
|
|
|
Regexp.new(regexp)
|
|
|
|
end
|
|
|
|
|
2014-01-13 19:01:50 +00:00
|
|
|
def self.rsync_single(machine, ssh_info, opts)
|
|
|
|
# Folder info
|
|
|
|
guestpath = opts[:guestpath]
|
|
|
|
hostpath = opts[:hostpath]
|
2014-01-13 19:34:49 +00:00
|
|
|
hostpath = File.expand_path(hostpath, machine.env.root_path)
|
2014-01-15 19:30:32 +00:00
|
|
|
hostpath = Vagrant::Util::Platform.fs_real_path(hostpath).to_s
|
2014-01-13 19:01:50 +00:00
|
|
|
|
Scrub Guest Paths for Windows Rsync leaving Dirty Paths for Winrm Mkdir
Windows offers no out-of-the-box rsync utility. By far, the most
commonly used external utilities for Windows rsync are built with the
GNU Cygwin libraries. The cost for this convenience is that rsync on
Windows has to be provided paths that begin “/cygdrive/c” rather than
“c:/“ like other Windows-API utilities. Compounding the situation,
rsync doesn’t create paths/to/sub/targets and so the vagrant plugin
code, when performing an rsync, is responsible for creating
intermediate directories in guest paths if there are any. Furthermore,
the mkdir utility in Windows is not another Cygwin utility like rsync
but the routine mkdir of Windows command.com. Therefore, while rsync
needs the /cygwin paths, mkdir uses the Windows paths. Later, the
chef_solo.rp provisioner running within the guest will expect to find
Windows-style paths in its solo.rb configuration file. Due to all this,
vagrant has to keep track of both the original, possibly dirty Windows
guest path and the cygwin-scrubbed guest path.
2015-08-18 16:56:13 +00:00
|
|
|
# if the guest has a guest path scrubber capability, use it
|
|
|
|
if machine.guest.capability?(:rsync_scrub_guestpath)
|
|
|
|
guestpath = machine.guest.capability(:rsync_scrub_guestpath, opts)
|
|
|
|
end
|
|
|
|
|
2014-01-16 05:13:08 +00:00
|
|
|
if Vagrant::Util::Platform.windows?
|
2014-03-10 01:21:30 +00:00
|
|
|
# rsync for Windows expects cygwin style paths, always.
|
|
|
|
hostpath = Vagrant::Util::Platform.cygwin_path(hostpath)
|
2014-01-16 05:13:08 +00:00
|
|
|
end
|
|
|
|
|
2014-01-16 05:22:37 +00:00
|
|
|
# Make sure the host path ends with a "/" to avoid creating
|
|
|
|
# a nested directory...
|
|
|
|
if !hostpath.end_with?("/")
|
|
|
|
hostpath += "/"
|
|
|
|
end
|
|
|
|
|
2014-03-13 01:43:59 +00:00
|
|
|
# Folder options
|
|
|
|
opts[:owner] ||= ssh_info[:username]
|
|
|
|
opts[:group] ||= ssh_info[:username]
|
|
|
|
|
2014-01-13 19:01:50 +00:00
|
|
|
# Connection information
|
|
|
|
username = ssh_info[:username]
|
|
|
|
host = ssh_info[:host]
|
2014-04-27 22:53:31 +00:00
|
|
|
proxy_command = ""
|
|
|
|
if ssh_info[:proxy_command]
|
|
|
|
proxy_command = "-o ProxyCommand='#{ssh_info[:proxy_command]}' "
|
|
|
|
end
|
|
|
|
|
2015-11-20 18:48:59 +00:00
|
|
|
# Create the path for the control sockets. We used to do this
|
|
|
|
# in the machine data dir but this can result in paths that are
|
|
|
|
# too long for unix domain sockets.
|
|
|
|
controlpath = File.join(Dir.tmpdir, "ssh.#{rand(1000)}")
|
|
|
|
|
2014-01-13 19:01:50 +00:00
|
|
|
rsh = [
|
2014-04-22 03:39:48 +00:00
|
|
|
"ssh -p #{ssh_info[:port]} " +
|
2014-04-26 17:52:39 +00:00
|
|
|
proxy_command +
|
2015-11-20 18:48:59 +00:00
|
|
|
"-o ControlMaster=auto " +
|
|
|
|
"-o ControlPath=#{controlpath} " +
|
|
|
|
"-o ControlPersist=10m " +
|
2014-04-22 03:39:48 +00:00
|
|
|
"-o StrictHostKeyChecking=no " +
|
2015-01-12 14:19:43 +00:00
|
|
|
"-o IdentitiesOnly=true " +
|
2014-04-22 03:39:48 +00:00
|
|
|
"-o UserKnownHostsFile=/dev/null",
|
2014-01-13 19:01:50 +00:00
|
|
|
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!
|
|
|
|
|
2014-03-06 19:27:58 +00:00
|
|
|
# Get the command-line arguments
|
|
|
|
args = nil
|
2014-05-21 02:23:39 +00:00
|
|
|
args = Array(opts[:args]).dup if opts[:args]
|
2014-05-09 16:31:31 +00:00
|
|
|
args ||= ["--verbose", "--archive", "--delete", "-z", "--copy-links"]
|
2014-03-06 19:27:58 +00:00
|
|
|
|
2014-04-05 09:01:09 +00:00
|
|
|
# On Windows, we have to set a default chmod flag to avoid permission issues
|
|
|
|
if Vagrant::Util::Platform.windows? && !args.any? { |arg| arg.start_with?("--chmod=") }
|
2014-04-05 16:05:39 +00:00
|
|
|
# Ensures that all non-masked bits get enabled
|
2014-04-02 16:07:00 +00:00
|
|
|
args << "--chmod=ugo=rwX"
|
2014-04-05 16:05:39 +00:00
|
|
|
|
|
|
|
# Remove the -p option if --archive is enabled (--archive equals -rlptgoD)
|
|
|
|
# otherwise new files will not have the destination-default permissions
|
|
|
|
args << "--no-perms" if args.include?("--archive") || args.include?("-a")
|
2014-04-02 16:07:00 +00:00
|
|
|
end
|
|
|
|
|
2014-04-25 19:00:12 +00:00
|
|
|
# Disable rsync's owner/group preservation (implied by --archive) unless
|
|
|
|
# specifically requested, since we adjust owner/group to match shared
|
|
|
|
# folder setting ourselves.
|
|
|
|
args << "--no-owner" unless args.include?("--owner") || args.include?("-o")
|
|
|
|
args << "--no-group" unless args.include?("--group") || args.include?("-g")
|
|
|
|
|
|
|
|
# Tell local rsync how to invoke remote rsync with sudo
|
2015-07-09 16:02:38 +00:00
|
|
|
rsync_path = opts[:rsync_path]
|
|
|
|
if !rsync_path && machine.guest.capability?(:rsync_command)
|
|
|
|
rsync_path = machine.guest.capability(:rsync_command)
|
|
|
|
end
|
|
|
|
if rsync_path
|
|
|
|
args << "--rsync-path"<< rsync_path
|
2014-04-25 19:00:12 +00:00
|
|
|
end
|
|
|
|
|
2014-01-13 19:01:50 +00:00
|
|
|
# Build up the actual command to execute
|
|
|
|
command = [
|
|
|
|
"rsync",
|
2014-03-06 19:27:58 +00:00
|
|
|
args,
|
2014-01-13 19:01:50 +00:00
|
|
|
"-e", rsh,
|
2014-03-06 19:27:58 +00:00
|
|
|
excludes.map { |e| ["--exclude", e] },
|
2014-01-13 19:01:50 +00:00
|
|
|
hostpath,
|
2014-03-06 19:27:58 +00:00
|
|
|
"#{username}@#{host}:#{guestpath}",
|
2014-01-13 19:01:50 +00:00
|
|
|
].flatten
|
|
|
|
|
|
|
|
# The working directory should be the root path
|
2014-03-06 19:27:58 +00:00
|
|
|
command_opts = {}
|
2014-01-13 19:01:50 +00:00
|
|
|
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
|
2015-01-05 20:36:00 +00:00
|
|
|
if opts.include?(:verbose)
|
2014-11-25 20:37:59 +00:00
|
|
|
machine.ui.info(I18n.t("vagrant.rsync_showing_output"));
|
2014-11-25 18:38:41 +00:00
|
|
|
end
|
2014-01-13 19:01:50 +00:00
|
|
|
|
|
|
|
# If we have tasks to do before rsyncing, do those.
|
|
|
|
if machine.guest.capability?(:rsync_pre)
|
|
|
|
machine.guest.capability(:rsync_pre, opts)
|
|
|
|
end
|
|
|
|
|
2015-01-05 20:36:00 +00:00
|
|
|
if opts.include?(:verbose)
|
2015-07-09 16:02:38 +00:00
|
|
|
command_opts[:notify] = [:stdout, :stderr]
|
|
|
|
r = Vagrant::Util::Subprocess.execute(*(command + [command_opts])) {
|
|
|
|
|io_name,data| data.each_line { |line|
|
|
|
|
machine.ui.info("rsync[#{io_name}] -> #{line}") }
|
2014-11-25 20:37:59 +00:00
|
|
|
}
|
2014-11-25 18:38:41 +00:00
|
|
|
else
|
|
|
|
r = Vagrant::Util::Subprocess.execute(*(command + [command_opts]))
|
|
|
|
end
|
|
|
|
|
2014-01-13 19:01:50 +00:00
|
|
|
if r.exit_code != 0
|
|
|
|
raise Vagrant::Errors::RSyncError,
|
2016-01-09 21:27:54 +00:00
|
|
|
command: command.map { |s| s.include?(' ') && s[0] != "'" && ('"' + s + '"') || s }.join(" "),
|
2014-01-13 19:01:50 +00:00
|
|
|
guestpath: guestpath,
|
|
|
|
hostpath: hostpath,
|
|
|
|
stderr: r.stderr
|
|
|
|
end
|
2014-03-13 01:43:59 +00:00
|
|
|
|
|
|
|
# If we have tasks to do after rsyncing, do those.
|
|
|
|
if machine.guest.capability?(:rsync_post)
|
|
|
|
machine.guest.capability(:rsync_post, opts)
|
|
|
|
end
|
2014-01-13 19:01:50 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|