diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 1f2329d91..2ed27db6b 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -324,7 +324,7 @@ module Vagrant # Returns the state of this machine. The state is queried from the # backing provider, so it can be any arbitrary symbol. # - # @return [Symbol] + # @return [MachineState] def state result = @provider.state raise Errors::MachineStateInvalid if !result.is_a?(MachineState) diff --git a/plugins/synced_folders/rsync/command/rsync.rb b/plugins/synced_folders/rsync/command/rsync.rb new file mode 100644 index 000000000..9b7b7fab6 --- /dev/null +++ b/plugins/synced_folders/rsync/command/rsync.rb @@ -0,0 +1,38 @@ +require 'optparse' + +require_relative "../helper" + +module VagrantPlugins + module SyncedFolderRSync + module Command + class Rsync < Vagrant.plugin("2", :command) + def self.synopsis + "syncs rsync synced folders to remote machine" + end + + def execute + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant rsync [vm-name]" + o.separator "" + end + + # Parse the options and return if we don't have any target. + argv = parse_options(opts) + return if !argv + + # Go through each machine and perform the rsync + error = false + with_target_vms(argv) do |machine| + if !machine.communicate.ready? + machine.ui.error(I18n.t("vagrant.rsync_communicator_not_ready")) + error = true + next + end + end + + return error ? 1 : 0 + end + end + end + end +end diff --git a/plugins/synced_folders/rsync/helper.rb b/plugins/synced_folders/rsync/helper.rb new file mode 100644 index 000000000..3c66fe04c --- /dev/null +++ b/plugins/synced_folders/rsync/helper.rb @@ -0,0 +1,66 @@ +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 + def self.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 diff --git a/plugins/synced_folders/rsync/plugin.rb b/plugins/synced_folders/rsync/plugin.rb index d74658c31..a39bccc68 100644 --- a/plugins/synced_folders/rsync/plugin.rb +++ b/plugins/synced_folders/rsync/plugin.rb @@ -9,6 +9,11 @@ module VagrantPlugins The Rsync synced folder plugin will sync folders via rsync. EOF + command("rsync", primary: false) do + require_relative "command/rsync" + Command::Rsync + end + synced_folder("rsync", 5) do require_relative "synced_folder" SyncedFolder diff --git a/plugins/synced_folders/rsync/synced_folder.rb b/plugins/synced_folders/rsync/synced_folder.rb index 87413afa9..416a1b06c 100644 --- a/plugins/synced_folders/rsync/synced_folder.rb +++ b/plugins/synced_folders/rsync/synced_folder.rb @@ -3,6 +3,8 @@ require "log4r" require "vagrant/util/subprocess" require "vagrant/util/which" +require_relative "helper" + module VagrantPlugins module SyncedFolderRSync class SyncedFolder < Vagrant.plugin("2", :synced_folder) @@ -33,65 +35,7 @@ module VagrantPlugins end 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 + RsyncHelper.rsync_single(machine, ssh_info, folder_opts) end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 0d41d8e23..5eb503e8d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -85,6 +85,9 @@ en: %{names} provisioner_cleanup: |- Running cleanup tasks for '%{name}' provisioner... + rsync_communicator_not_ready: |- + The machine is reporting that it is not ready for rsync to + communiate with it. Verify that this machine is properly running. rsync_folder: |- Rsyncing folder: %{hostpath} => %{guestpath} rsync_folder_excludes: " - Exclude: %{excludes}" diff --git a/test/unit/plugins/synced_folders/rsync/helper_test.rb b/test/unit/plugins/synced_folders/rsync/helper_test.rb new file mode 100644 index 000000000..f6633b911 --- /dev/null +++ b/test/unit/plugins/synced_folders/rsync/helper_test.rb @@ -0,0 +1,100 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/synced_folders/rsync/helper") + +describe VagrantPlugins::SyncedFolderRSync::RsyncHelper 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(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + + subject { described_class } + + before do + machine.stub(guest: guest) + 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 diff --git a/test/unit/plugins/synced_folders/rsync/synced_folder_test.rb b/test/unit/plugins/synced_folders/rsync/synced_folder_test.rb index 0c2aba551..2b7df8781 100644 --- a/test/unit/plugins/synced_folders/rsync/synced_folder_test.rb +++ b/test/unit/plugins/synced_folders/rsync/synced_folder_test.rb @@ -16,6 +16,8 @@ describe VagrantPlugins::SyncedFolderRSync::SyncedFolder do let(:host) { double("host") } let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + let(:helper_class) { VagrantPlugins::SyncedFolderRSync::RsyncHelper } + before do machine.env.stub(host: host) machine.stub(guest: guest) @@ -55,7 +57,7 @@ describe VagrantPlugins::SyncedFolderRSync::SyncedFolder do ] folders.each do |_, opts| - subject.should_receive(:rsync_single). + helper_class.should_receive(:rsync_single). with(machine, ssh_info, opts). ordered end @@ -63,81 +65,4 @@ describe VagrantPlugins::SyncedFolderRSync::SyncedFolder do 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