vagrant-syncer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +21 -0
  6. data/README.md +81 -0
  7. data/Rakefile +3 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +7 -0
  10. data/build_and_install.sh +3 -0
  11. data/example/Vagrantfile +61 -0
  12. data/example/files/directory/excluded_directory/yes.txt +0 -0
  13. data/example/files/directory/file_in_directory.txt +0 -0
  14. data/example/files/directory/nested_directory/excluded_directory/yes.txt +0 -0
  15. data/example/files/directory/nested_directory/excluded_file.txt +0 -0
  16. data/example/files/directory/nested_directory/excluded_nested_directory/no.txt +0 -0
  17. data/example/files/directory/nested_directory/excluded_nested_file.txt +0 -0
  18. data/example/files/directory/nested_directory/yes.txt +0 -0
  19. data/example/files/directory/nested_directory2/excluded_nested_directory/no.txt +0 -0
  20. data/example/files/directory/nested_directory2/excluded_nested_file.txt +0 -0
  21. data/example/files/directory/nested_directory2/yes.txt +0 -0
  22. data/example/files/directory/sync_me.not +0 -0
  23. data/example/files/excluded_directory/no.txt +0 -0
  24. data/example/files/excluded_file.txt +0 -0
  25. data/example/files/file.not +0 -0
  26. data/example/files/yes.txt +0 -0
  27. data/lib/syncer/actions.rb +24 -0
  28. data/lib/syncer/commands/syncer.rb +22 -0
  29. data/lib/syncer/config.rb +27 -0
  30. data/lib/syncer/listeners/fsevents.rb +40 -0
  31. data/lib/syncer/listeners/inotify.rb +38 -0
  32. data/lib/syncer/listeners/listen.rb +60 -0
  33. data/lib/syncer/machine.rb +30 -0
  34. data/lib/syncer/path.rb +71 -0
  35. data/lib/syncer/plugin.rb +29 -0
  36. data/lib/syncer/syncers/rsync.rb +161 -0
  37. data/lib/syncer/version.rb +5 -0
  38. data/lib/vagrant-syncer.rb +23 -0
  39. data/locales/en.yml +8 -0
  40. data/vagrant-syncer.gemspec +23 -0
  41. metadata +114 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 78956c78a675ea34cb1458cf9a2961a6e1cffe14
4
+ data.tar.gz: 16a0bc9b8a5a792ea5fda9ee0fc3e095546b4036
5
+ SHA512:
6
+ metadata.gz: 69bb83ca7c7e906fffde1dcddea27fd0608a0a945f0f70c6c7179b65fae0548d1bbf950d8a282709881032ad4842887f5fb498aba5655c39c545132bc3c50f04
7
+ data.tar.gz: 479e505688e4886a3709c37586add4a2d1f34cfa9c9f14f4819491069edad763a6227167ce1fd2ef16757a3b5a6855fd8f45b3921efb4d8c6cf46f9602a8421e
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ example/.vagrant
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vagrant-syncer.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "vagrant", git: "https://github.com/mitchellh/vagrant.git", tag: 'v1.7.4'
8
+ end
9
+
10
+ group :plugins do
11
+ gem "vagrant-syncer", path: "."
12
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2015 Anssi Syrjäsalo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # vagrant syncer
2
+
3
+ A Vagrant synced folder plugin that is an optimized implementation of [Vagrant rsync(-auto)](https://github.com/mitchellh/vagrant/tree/b721eb62cfbfa93895d0d4cf019436ab6b1df05d/plugins/synced_folders/rsync), based heavily on [vagrant-gatling-rsync](https://github.com/smerrill/vagrant-gatling-rsync)'s great listener implementations for watching large hierarchies.
4
+
5
+ Vagrant syncer forks [Vagrant's RsyncHelper](https://github.com/mitchellh/vagrant/blob/b721eb62cfbfa93895d0d4cf019436ab6b1df05d/plugins/synced_folders/rsync/helper.rb)
6
+ to make it (c)leaner, instead of using the class like [vagrant-gatling-rsync](https://github.com/smerrill/vagrant-gatling-rsync) does.
7
+
8
+ If the optimizations seem to work in heavy use, I'll see if (some of) them
9
+ can be merged to Vagrant core and be submitted as pull requests to
10
+ [the official Vagrant repo](https://github.com/mitchellh/vagrant).
11
+
12
+
13
+ ## Installation
14
+
15
+ vagrant plugin install vagrant-syncer
16
+
17
+
18
+ ## Configuration
19
+
20
+ All the [rsync synced folder settings](https://docs.vagrantup.com/v2/synced-folders/rsync.html)
21
+ are supported. They also have the same default values.
22
+
23
+ See [the example Vagrantfile](https://github.com/asyrjasalo/vagrant-syncer/blob/master/example/Vagrantfile)
24
+ for additional plugin specific ```config.syncer``` settings and their default values.
25
+
26
+
27
+ ## Usage
28
+
29
+ vagrant syncer
30
+
31
+ ## Improvements over rsync(-auto)
32
+
33
+ - The plugin has leaner rsync implementation with most of the rsync command
34
+ argument constructing already handled in the class initializer and not sync-time
35
+ - Uses [rb-fsevent](https://github.com/thibaudgg/rb-fsevent) and
36
+ [rb-inotify](https://github.com/nex3/rb-inotify) gems underneath for
37
+ performance on OS X and GNU/Linux respectively, instead of using Listen.
38
+ On Windows, Listen is used though as using wdm still needs some testing.
39
+ - Allow defining additional SSH arguments to rsync in Vagrantfile using
40
+ ```config.syncer.ssh_args```. This can be used for e.g. disabling SSH
41
+ compression to lower CPU overhead.
42
+ - Runs ```vagrant syncer``` to start watching changes after vagrant up, reload
43
+ and resume, if ```config.syncer.run_on_startup``` set to ```true```
44
+ in Vagrantfile
45
+ - Vagrant's implementation assumes that the primary group of the SSH user
46
+ has the same name as the user, if rsync option ```group``` is not explicitly
47
+ defined. This plugin queries the user's real primary group from the guest.
48
+ - Hooking Vagrant's ```:rsync_pre``` is removed, as this unnecessarily runs mkdir
49
+ to create the target directory, which rsync command creates sync-time anyway.
50
+
51
+
52
+ ## Development
53
+
54
+ Fork this repository, clone it and install Ruby 2.2.3, using e.g. [rbenv](https://github.com/sstephenson/rbenv):
55
+
56
+ cd vagrant-syncer
57
+ rbenv install $(cat .ruby-version)
58
+ gem install bundler -v1.10.5
59
+ bundle install
60
+
61
+ Then use it with:
62
+
63
+ bundle exec vagrant syncer
64
+
65
+ Or outside the bundle:
66
+
67
+ ./build_and_install.sh
68
+ vagrant syncer
69
+
70
+ I'll kindly take pull requests as well.
71
+
72
+ ## Credits
73
+
74
+ [vagrant-syncer](https://github.com/asyrjasalo/vagrant-syncer) was originally put together by Anssi Syrjäsalo.
75
+
76
+ Thanks to [Steven Merrill's](https://github.com/smerrill) (@stevenmerrill) [vagrant-gatling-rsync](https://github.com/smerrill/vagrant-gatling-rsync)
77
+ for [the listener implementations](https://github.com/smerrill/vagrant-gatling-rsync/tree/master/lib/vagrant-gatling-rsync/listen) and the original idea to tap into [rb-fsevent](https://github.com/thibaudgg/rb-fsevent) (OS X)
78
+ and [rb-inotify](https://github.com/nex3/rb-inotify) (GNU/Linux) for non-CPU hog watching of hierarchies with 10,000-100,000 files.
79
+
80
+ And to [Hashicorp](https://github.com/hashicorp) for [Vagrant](https://github.com/mitchellh/vagrant), even though its
81
+ future will likely be overshadowed by [Otto](https://github.com/hashicorp/otto).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ Bundler::GemHelper.install_tasks
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "vagrant/syncer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+
3
+ bundle exec rake build && vagrant plugin install pkg/vagrant-syncer-*.gem
@@ -0,0 +1,61 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ Vagrant.configure(2) do |config|
5
+ config.vm.box = 'ubuntu/trusty64'
6
+
7
+ # Disable checking updates for faster testing
8
+ config.vm.box_check_update = false
9
+ config.vbguest.auto_update = false if Vagrant.has_plugin?("vagrant-vbguest")
10
+
11
+ config.vm.network 'private_network', ip: '10.10.10.10'
12
+
13
+ config.vm.synced_folder '.', '/vagrant', disabled: true
14
+
15
+ config.vm.synced_folder "files", "/home/vagrant/files", type: 'rsync',
16
+ rsync__args: [
17
+ "--archive",
18
+ "--delete",
19
+ "--force",
20
+ "--numeric-ids"
21
+ ],
22
+ rsync__exclude: [
23
+ "/excluded_file.txt",
24
+ "/excluded_directory",
25
+ "excluded_nested_file.txt",
26
+ "excluded_nested_directory",
27
+ "*.not",
28
+ ".git"
29
+ ],
30
+ rsync__rsync_path: 'rsync',
31
+ rsync__verbose: true
32
+
33
+ config.ssh.username = 'vagrant'
34
+ config.ssh.forward_agent = true
35
+
36
+
37
+ ### Vagrant syncer specific settings are introduced below
38
+
39
+ # How often (in seconds) to read file system events
40
+ # Default: 0.2. Minimum accepted value is 0.2.
41
+ config.syncer.interval = 0.2
42
+
43
+ # Whether or not to start watching after the machine is up, reloaded or resumed
44
+ # Default: true
45
+ config.syncer.run_on_startup = true
46
+
47
+ # Whether or not to show the file system events
48
+ # Default: false
49
+ config.syncer.show_events = false
50
+
51
+ # Optional SSH arguments passed to rsync for lower CPU load
52
+ # Default: -o StrictHostKeyChecking=no, -o IdentitiesOnly=true, -o UserKnownHostsFile=/dev/null
53
+ config.syncer.ssh_args = [
54
+ '-o StrictHostKeyChecking=no',
55
+ '-o IdentitiesOnly=true',
56
+ '-o UserKnownHostsFile=/dev/null',
57
+ '-c arcfour,blowfish-cbc',
58
+ '-o Compression=no',
59
+ '-x'
60
+ ]
61
+ end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,24 @@
1
+ module Vagrant
2
+ module Syncer
3
+ module Actions
4
+ class StartSyncer
5
+
6
+ def initialize(app, env)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ @app.call(env)
12
+
13
+ return unless env[:machine].config.syncer.run_on_startup
14
+
15
+ # If Vagrant up/reload/resume exited successfully, run this syncer
16
+ at_exit do
17
+ env[:machine].env.cli("syncer") if $!.status == 0
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module Vagrant
2
+ module Syncer
3
+ module Commands
4
+ class Syncer < Vagrant.plugin(2, :command)
5
+
6
+ def self.synopsis
7
+ "start auto-rsyncing"
8
+ end
9
+
10
+ def execute
11
+ with_target_vms do |machine|
12
+ machine = Machine.new(machine)
13
+ # machine.full_sync
14
+ machine.listen
15
+ end
16
+ 0
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ module Vagrant
2
+ module Syncer
3
+ class Config < Vagrant.plugin(2, :config)
4
+
5
+ attr_accessor :interval, :run_on_startup, :show_events, :ssh_args
6
+
7
+ def initialize
8
+ @interval = UNSET_VALUE
9
+ @show_events = UNSET_VALUE
10
+ @ssh_args = UNSET_VALUE
11
+ @run_on_startup = UNSET_VALUE
12
+ end
13
+
14
+ def finalize!
15
+ @interval = 0.2 if @interval == UNSET_VALUE || @interval <= 0.2
16
+ @run_on_startup = true if @run_on_startup == UNSET_VALUE
17
+ @show_events = false if @show_events == UNSET_VALUE
18
+ @ssh_args = [
19
+ '-o StrictHostKeyChecking=no',
20
+ '-o IdentitiesOnly=true',
21
+ '-o UserKnownHostsFile=/dev/null'
22
+ ] if @ssh_args == UNSET_VALUE
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ require "rb-fsevent"
2
+
3
+ module Vagrant
4
+ module Syncer
5
+ module Listeners
6
+ class FSEvents
7
+
8
+ def initialize(absolute_path, excludes, settings, callback)
9
+ @absolute_path = absolute_path
10
+ @settings = settings.merge!(no_defer: false)
11
+ @callback = callback
12
+ # rb-fsevent does not support excludes
13
+ end
14
+
15
+ def run
16
+ changes = Queue.new
17
+ fsevent = FSEvent.new
18
+ fsevent.watch @absolute_path, @settings do |paths|
19
+ paths.each { |path| changes << path }
20
+ end
21
+ Thread.new { fsevent.run }
22
+
23
+ loop do
24
+ directories = Set.new
25
+ begin
26
+ loop do
27
+ change = Timeout::timeout(@settings[:latency]) { changes.pop }
28
+ directories << change unless change.nil?
29
+ end
30
+ rescue Timeout::Error, ThreadError
31
+ end
32
+
33
+ @callback.call(directories.to_a) unless directories.empty?
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,38 @@
1
+ require "rb-inotify"
2
+
3
+ module Vagrant
4
+ module Syncer
5
+ module Listeners
6
+ class INotify
7
+
8
+ def initialize(absolute_path, excludes, settings, callback)
9
+ @absolute_path = absolute_path
10
+ @settings = settings
11
+ @callback = callback
12
+ # rb-inotify does not support excludes
13
+ end
14
+
15
+ def run
16
+ notifier = ::INotify::Notifier.new
17
+ notifier.watch(@absolute_path, :modify, :create, :delete, :recursive) {}
18
+
19
+ loop do
20
+ directories = Set.new
21
+ begin
22
+ loop do
23
+ events = []
24
+ events = Timeout::timeout(@settings[:latency]) {
25
+ notifier.read_events
26
+ }
27
+ events.each { |e| directories << e.absolute_name }
28
+ end
29
+ rescue Timeout::Error
30
+ end
31
+
32
+ @callback.call(directories.to_a) unless directories.empty?
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ require 'listen'
2
+ require 'vagrant/util/busy'
3
+
4
+ module Vagrant
5
+ module Syncer
6
+ module Listeners
7
+ class Listen
8
+
9
+ def self.excludes_to_listen(exclude)
10
+ exclude = exclude.gsub('**', '"GLOBAL"')
11
+ exclude = exclude.gsub('*', '"PATH"')
12
+
13
+ if exclude.start_with?('/')
14
+ pattern = "^#{Regexp.escape(exclude[1..-1])}"
15
+ else
16
+ pattern = Regexp.escape(exclude)
17
+ end
18
+
19
+ pattern = pattern.gsub('"PATH"', "[^/]*")
20
+ pattern = pattern.gsub('"GLOBAL"', ".*")
21
+
22
+ Regexp.new(pattern)
23
+ end
24
+
25
+ def initialize(absolute_path, excludes, settings, callback)
26
+ @absolute_path = absolute_path
27
+ @settings = settings
28
+ @callback = callback
29
+
30
+ if excludes
31
+ @settings[:ignore!] = []
32
+ excludes.each do |pattern|
33
+ @settings[:ignore!] << self.class.excludes_to_listen(pattern.to_s)
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ def run
40
+ listener = ::Listen.to(@absolute_path, @settings) do |mod, add, rem|
41
+ @callback.call(mod + add + rem)
42
+ end
43
+
44
+ queue = Queue.new
45
+ callback = lambda do
46
+ Thread.new { queue << true }
47
+ end
48
+
49
+ # Run the listener in a busy block, exit once we receive an interrupt
50
+ Vagrant::Util::Busy.busy(callback) do
51
+ listener.start
52
+ queue.pop
53
+ listener.stop if listener.state != :stopped
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ require 'vagrant/action/builtin/mixin_synced_folders'
2
+
3
+ module Vagrant
4
+ module Syncer
5
+ class Machine
6
+
7
+ include Vagrant::Action::Builtin::MixinSyncedFolders
8
+
9
+ def initialize(machine)
10
+ @paths = []
11
+
12
+ synced_folders = synced_folders(machine)[:rsync]
13
+ return unless synced_folders
14
+
15
+ synced_folders.each do |id, folder_opts|
16
+ @paths << Path.new(folder_opts, machine)
17
+ end
18
+ end
19
+
20
+ def full_sync
21
+ @paths.each(&:initial_sync)
22
+ end
23
+
24
+ def listen
25
+ @paths.each(&:listen)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ require 'vagrant/util/platform'
2
+
3
+ require_relative 'syncers/rsync'
4
+
5
+ module Vagrant
6
+ module Syncer
7
+ class Path
8
+
9
+ def initialize(path_opts, machine)
10
+ @logger = machine.ui
11
+ @source_path = path_opts[:hostpath]
12
+ @syncer = Syncers::Rsync.new(path_opts, machine)
13
+ @absolute_path = File.expand_path(@source_path, machine.env.root_path)
14
+
15
+ @listener_verbose = machine.config.syncer.show_events
16
+ @listener_interval = machine.config.syncer.interval
17
+
18
+ case Vagrant::Util::Platform.platform
19
+ when /darwin/
20
+ require_relative 'listeners/fsevents'
21
+ @listener_class = Vagrant::Syncer::Listeners::FSEvents
22
+ when /linux/
23
+ require_relative 'listeners/inotify'
24
+ @listener_class = Vagrant::Syncer::Listeners::INotify
25
+ else
26
+ require_relative 'listeners/listen'
27
+ @listener_class = Vagrant::Syncer::Listeners::Listen
28
+ end
29
+
30
+ @listener_name = @listener_class.to_s.gsub(/^.*::/, '')
31
+
32
+ listener_settings = {
33
+ latency: @listener_interval
34
+ }
35
+
36
+ @listener = @listener_class.new(
37
+ @absolute_path,
38
+ path_opts[:rsync__excludes],
39
+ listener_settings,
40
+ change_callback
41
+ )
42
+ end
43
+
44
+ def initial_sync
45
+ @logger.info(I18n.t('syncer.states.initial', path: @absolute_path))
46
+ @syncer.sync
47
+ end
48
+
49
+ def listen
50
+ @logger.info(I18n.t('syncer.states.watching', {
51
+ path: @absolute_path,
52
+ listener: @listener_name,
53
+ interval: @listener_interval
54
+ }))
55
+ @listener.run
56
+ end
57
+
58
+ private
59
+
60
+ def change_callback
61
+ Proc.new do |changed|
62
+ if @listener_verbose
63
+ @logger.info(@listener_name + ": " + changed.join(', '))
64
+ end
65
+ @syncer.sync(changed)
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,29 @@
1
+ module Vagrant
2
+ module Syncer
3
+ class Plugin < Vagrant.plugin(2)
4
+
5
+ name "Syncer"
6
+
7
+ description <<-DESC
8
+ Watches for changed files on the host and rsyncs them to the machine.
9
+ DESC
10
+
11
+ config "syncer" do
12
+ require 'syncer/config'
13
+ Vagrant::Syncer::Config
14
+ end
15
+
16
+ command "syncer" do
17
+ require 'syncer/commands/syncer'
18
+ Vagrant::Syncer::Commands::Syncer
19
+ end
20
+
21
+ ["machine_action_up", "machine_action_reload", "machine_action_resume"].each do |action|
22
+ action_hook "start-syncer", action do |hook|
23
+ hook.append Vagrant::Syncer::Actions::StartSyncer
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,161 @@
1
+ require "vagrant/util/platform"
2
+ require "vagrant/util/subprocess"
3
+
4
+ module Vagrant
5
+ module Syncer
6
+ module Syncers
7
+ class Rsync
8
+
9
+ def initialize(path_opts, machine)
10
+ @machine = machine
11
+ @logger = machine.ui
12
+
13
+ @machine_path = machine.env.root_path.to_s
14
+ @host_path = parse_host_path(path_opts[:hostpath])
15
+ @rsync_args = parse_rsync_args(path_opts[:rsync__args], path_opts[:rsync__rsync_path])
16
+ @rsync_verbose = path_opts[:rsync__verbose] || false
17
+ @ssh_command = parse_ssh_command(machine.config.syncer.ssh_args)
18
+ @exclude_args = parse_exclude_args(path_opts[:rsync__exclude])
19
+
20
+ ssh_username = machine.ssh_info[:username]
21
+ ssh_host = machine.ssh_info[:host]
22
+ @ssh_target = "#{ssh_username}@#{ssh_host}:#{path_opts[:guestpath]}"
23
+
24
+ @vagrant_command_opts = {
25
+ workdir: @machine_path
26
+ }
27
+
28
+ @vagrant_rsync_opts = {
29
+ guestpath: path_opts[:guestpath],
30
+ chown: path_opts[:rsync__chown],
31
+ owner: path_opts[:owner],
32
+ group: path_opts[:group]
33
+ }
34
+ @vagrant_rsync_opts[:chown] ||= true
35
+ @vagrant_rsync_opts[:owner] ||= ssh_username
36
+ if @vagrant_rsync_opts[:group].nil?
37
+ machine.communicate.execute('id -gn') do |type, output|
38
+ @vagrant_rsync_opts[:group] = output.chomp if type == :stdout
39
+ end
40
+ end
41
+ end
42
+
43
+ def sync(changed_paths=nil)
44
+ changed_paths ||= [@host_path]
45
+
46
+ rsync_command = [
47
+ "rsync",
48
+ @rsync_args,
49
+ "-e", @ssh_command,
50
+ changed_paths.map { |path| ["--include", path] },
51
+ @exclude_args,
52
+ @host_path,
53
+ @ssh_target
54
+ ].flatten
55
+
56
+ rsync_vagrant_command = rsync_command + [@vagrant_command_opts]
57
+ if @rsync_verbose
58
+ @vagrant_command_opts[:notify] = [:stdout, :stderr]
59
+ result = Vagrant::Util::Subprocess.execute(*rsync_vagrant_command) do |io_name, data|
60
+ data.each_line do |line|
61
+ if io_name == :stdout
62
+ @logger.success("Rsync: #{line}")
63
+ elsif io_name == :stderr && !line =~ /Permanently added/
64
+ @logger.warn("Rsync: #{line}")
65
+ end
66
+ end
67
+ end
68
+ else
69
+ result = Vagrant::Util::Subprocess.execute(*rsync_vagrant_command)
70
+ end
71
+
72
+ if result.exit_code != 0
73
+ @logger.error(I18n.t('syncer.rsync.failed', error: result.stderr))
74
+ @logger.error(I18n.t('syncer.rsync.failed_command', command: rsync_command.join(' ')))
75
+ return
76
+ end
77
+
78
+ # Set owner/group after the files are transferred
79
+ if @machine.guest.capability?(:rsync_post)
80
+ @machine.guest.capability(:rsync_post, @vagrant_rsync_opts)
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def parse_host_path(host_dir)
87
+ abs_host_path = File.expand_path(host_dir, @machine_path)
88
+ abs_host_path = Vagrant::Util::Platform.fs_real_path(abs_host_path).to_s
89
+
90
+ # Rsync on Windows expects Cygwin style paths
91
+ if Vagrant::Util::Platform.windows?
92
+ abs_host_path = Vagrant::Util::Platform.cygwin_path(abs_host_path)
93
+ end
94
+
95
+ # Ensure path ends with '/' to prevent creating directory inside directory
96
+ abs_host_path += "/" if !abs_host_path.end_with?("/")
97
+
98
+ abs_host_path
99
+ end
100
+
101
+ def parse_exclude_args(excludes=nil)
102
+ excludes ||= []
103
+ excludes << '.vagrant/' # in any case, exclude .vagrant directory
104
+ excludes.uniq.map { |e| ["--exclude", e] }
105
+ end
106
+
107
+ def parse_ssh_command(ssh_args)
108
+ proxy_command = ""
109
+ if @machine.ssh_info[:proxy_command]
110
+ proxy_command = "-o ProxyCommand='#{@machine.ssh_info[:proxy_command]}' "
111
+ end
112
+
113
+ ssh_command = [
114
+ "ssh -p #{@machine.ssh_info[:port]} " +
115
+ proxy_command +
116
+ ssh_args.join(' '),
117
+ @machine.ssh_info[:private_key_path].map { |p| "-i '#{p}'" },
118
+ ].flatten.join(' ')
119
+ end
120
+
121
+ def parse_rsync_args(rsync_args=nil, rsync_path=nil)
122
+ rsync_args ||= ["--archive", "--delete", "--compress", "--copy-links", "--verbose"]
123
+
124
+ # This is the default rsync output unless overridden by user
125
+ rsync_args.unshift("--out-format=%L%n")
126
+
127
+ rsync_chmod_args_given = rsync_args.any? { |arg| arg.start_with?("--chmod=") }
128
+
129
+ # On Windows, enable all non-masked bits to avoid permission issues
130
+ if Vagrant::Util::Platform.windows? && !rsync_chmod_args_given
131
+ rsync_args << "--chmod=ugo=rwX"
132
+
133
+ # Remove the -p option if --archive (equals -rlptgoD) is given
134
+ # Otherwise new files won't get the destination-default permissions
135
+ if rsync_args.include?("--archive") || rsync_args.include?("-a")
136
+ rsync_args << "--no-perms"
137
+ end
138
+ end
139
+
140
+ # Disable rsync's owner/group preservation (implied by --archive) unless
141
+ # explicitly requested, since we adjust owner/group later ourselves
142
+ unless rsync_args.include?("--owner") || rsync_args.include?("-o")
143
+ rsync_args << "--no-owner"
144
+ end
145
+ unless rsync_args.include?("--group") || rsync_args.include?("-g")
146
+ rsync_args << "--no-group"
147
+ end
148
+
149
+ # Invoke remote rsync with sudo to allow owner and group settings to work
150
+ if !rsync_path && @machine.guest.capability?(:rsync_command)
151
+ rsync_path = @machine.guest.capability(:rsync_command)
152
+ end
153
+ rsync_args << "--rsync-path" << rsync_path if rsync_path
154
+
155
+ rsync_args
156
+ end
157
+
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,5 @@
1
+ module Vagrant
2
+ module Syncer
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "This plugin must be run within Vagrant."
5
+ end
6
+
7
+ require 'syncer/actions'
8
+ require 'syncer/machine'
9
+ require 'syncer/path'
10
+ require 'syncer/plugin'
11
+ require 'syncer/version'
12
+
13
+ module Vagrant
14
+ module Syncer
15
+
16
+ def self.source_root
17
+ @source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
18
+ end
19
+
20
+ I18n.load_path << File.expand_path("locales/en.yml", source_root)
21
+ I18n.reload!
22
+ end
23
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,8 @@
1
+ en:
2
+ syncer:
3
+ states:
4
+ initial: "Syncing %{path} to get the target up to date."
5
+ watching: "Watching %{path} for changes (via %{listener}) every %{interval} seconds."
6
+ rsync:
7
+ failed: "Rsync failed: %{error}"
8
+ failed_command: "The failed command was: %{command}"
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'syncer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "vagrant-syncer"
8
+ spec.version = Vagrant::Syncer::VERSION
9
+ spec.authors = ["Anssi Syrjäsalo"]
10
+ spec.email = ["anssi.syrjasalo@gmail.com"]
11
+ spec.summary = %q{Optimized Vagrant rsync-auto}
12
+ spec.description = %q{A Vagrant synced folder plugin with watchers for large file hierarchies and (c)leaner rsync-auto.}
13
+ spec.homepage = "https://github.com/asyrjasalo/vagrant-syncer"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake"
23
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-syncer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Anssi Syrjäsalo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A Vagrant synced folder plugin with watchers for large file hierarchies
42
+ and (c)leaner rsync-auto.
43
+ email:
44
+ - anssi.syrjasalo@gmail.com
45
+ executables:
46
+ - console
47
+ - setup
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - ".gitignore"
52
+ - ".ruby-version"
53
+ - Gemfile
54
+ - LICENSE
55
+ - README.md
56
+ - Rakefile
57
+ - bin/console
58
+ - bin/setup
59
+ - build_and_install.sh
60
+ - example/Vagrantfile
61
+ - example/files/directory/excluded_directory/yes.txt
62
+ - example/files/directory/file_in_directory.txt
63
+ - example/files/directory/nested_directory/excluded_directory/yes.txt
64
+ - example/files/directory/nested_directory/excluded_file.txt
65
+ - example/files/directory/nested_directory/excluded_nested_directory/no.txt
66
+ - example/files/directory/nested_directory/excluded_nested_file.txt
67
+ - example/files/directory/nested_directory/yes.txt
68
+ - example/files/directory/nested_directory2/excluded_nested_directory/no.txt
69
+ - example/files/directory/nested_directory2/excluded_nested_file.txt
70
+ - example/files/directory/nested_directory2/yes.txt
71
+ - example/files/directory/sync_me.not
72
+ - example/files/excluded_directory/no.txt
73
+ - example/files/excluded_file.txt
74
+ - example/files/file.not
75
+ - example/files/yes.txt
76
+ - lib/syncer/actions.rb
77
+ - lib/syncer/commands/syncer.rb
78
+ - lib/syncer/config.rb
79
+ - lib/syncer/listeners/fsevents.rb
80
+ - lib/syncer/listeners/inotify.rb
81
+ - lib/syncer/listeners/listen.rb
82
+ - lib/syncer/machine.rb
83
+ - lib/syncer/path.rb
84
+ - lib/syncer/plugin.rb
85
+ - lib/syncer/syncers/rsync.rb
86
+ - lib/syncer/version.rb
87
+ - lib/vagrant-syncer.rb
88
+ - locales/en.yml
89
+ - vagrant-syncer.gemspec
90
+ homepage: https://github.com/asyrjasalo/vagrant-syncer
91
+ licenses:
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.4.5.1
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Optimized Vagrant rsync-auto
114
+ test_files: []