vagrant-syncer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []