vagrant-syncer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.ruby-version +1 -0
- data/Gemfile +12 -0
- data/LICENSE +21 -0
- data/README.md +81 -0
- data/Rakefile +3 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/build_and_install.sh +3 -0
- data/example/Vagrantfile +61 -0
- data/example/files/directory/excluded_directory/yes.txt +0 -0
- data/example/files/directory/file_in_directory.txt +0 -0
- data/example/files/directory/nested_directory/excluded_directory/yes.txt +0 -0
- data/example/files/directory/nested_directory/excluded_file.txt +0 -0
- data/example/files/directory/nested_directory/excluded_nested_directory/no.txt +0 -0
- data/example/files/directory/nested_directory/excluded_nested_file.txt +0 -0
- data/example/files/directory/nested_directory/yes.txt +0 -0
- data/example/files/directory/nested_directory2/excluded_nested_directory/no.txt +0 -0
- data/example/files/directory/nested_directory2/excluded_nested_file.txt +0 -0
- data/example/files/directory/nested_directory2/yes.txt +0 -0
- data/example/files/directory/sync_me.not +0 -0
- data/example/files/excluded_directory/no.txt +0 -0
- data/example/files/excluded_file.txt +0 -0
- data/example/files/file.not +0 -0
- data/example/files/yes.txt +0 -0
- data/lib/syncer/actions.rb +24 -0
- data/lib/syncer/commands/syncer.rb +22 -0
- data/lib/syncer/config.rb +27 -0
- data/lib/syncer/listeners/fsevents.rb +40 -0
- data/lib/syncer/listeners/inotify.rb +38 -0
- data/lib/syncer/listeners/listen.rb +60 -0
- data/lib/syncer/machine.rb +30 -0
- data/lib/syncer/path.rb +71 -0
- data/lib/syncer/plugin.rb +29 -0
- data/lib/syncer/syncers/rsync.rb +161 -0
- data/lib/syncer/version.rb +5 -0
- data/lib/vagrant-syncer.rb +23 -0
- data/locales/en.yml +8 -0
- data/vagrant-syncer.gemspec +23 -0
- 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
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
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
data/example/Vagrantfile
ADDED
@@ -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
|
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
|
data/lib/syncer/path.rb
ADDED
@@ -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,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: []
|