vagrant-reflect 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/lib/vagrant-reflect/command/reflect.rb +51 -38
- data/lib/vagrant-reflect/configuration/reflect.rb +25 -0
- data/lib/vagrant-reflect/patched/subprocess.rb +3 -0
- data/lib/vagrant-reflect/plugin.rb +5 -0
- data/lib/vagrant-reflect/util/excludes.rb +53 -0
- data/lib/vagrant-reflect/util/reflector.rb +82 -0
- data/lib/vagrant-reflect/util/shell.rb +136 -0
- data/lib/vagrant-reflect/util/sync.rb +146 -0
- data/lib/vagrant-reflect/version.rb +1 -1
- metadata +7 -3
- data/lib/vagrant-reflect/util/syncer.rb +0 -327
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e84bae9f41ff115636ac176da7601c803d83389d
|
4
|
+
data.tar.gz: 627b6a9da251a1da3998de39a25b6fd82564b9a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b852921fb6bce29d04984873f0a543e8982f1ca0fdc4521cbaddd7957c3f5351d55f8515a34e2d44511aeb9ed1262cf3707fbb64fe5d1f291194f21d8bab839
|
7
|
+
data.tar.gz: adb7c4d480eb25fd9288bf9db0b7bb23fe6bb249779262ab3570010b0ec7186ca01d2fa7942225f4e02b84740e77f71b223cb51ccb1e40eb4f152bb762f82f15
|
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'log4r'
|
2
2
|
require 'optparse'
|
3
3
|
require 'thread'
|
4
|
+
require 'date'
|
4
5
|
|
5
6
|
require 'vagrant/action/builtin/mixin_synced_folders'
|
6
7
|
require 'vagrant/util/busy'
|
7
8
|
require 'vagrant/util/platform'
|
8
9
|
|
9
|
-
require_relative '../util/
|
10
|
+
require_relative '../util/excludes'
|
11
|
+
require_relative '../util/sync'
|
10
12
|
|
11
13
|
require 'driskell-listen'
|
12
14
|
|
@@ -26,21 +28,9 @@ module VagrantReflect
|
|
26
28
|
poll: false,
|
27
29
|
incremental: true
|
28
30
|
}
|
31
|
+
|
29
32
|
opts = OptionParser.new do |o|
|
30
33
|
o.banner = 'Usage: vagrant reflect [vm-name]'
|
31
|
-
o.separator ''
|
32
|
-
o.separator 'Options:'
|
33
|
-
o.separator ''
|
34
|
-
|
35
|
-
o.on('--[no-]poll', 'Force polling filesystem (slow)') do |poll|
|
36
|
-
options[:poll] = poll
|
37
|
-
end
|
38
|
-
o.on(
|
39
|
-
'--[no-]incremental',
|
40
|
-
'Perform incremental copies of changes where possible (fast)'
|
41
|
-
) do |incremental|
|
42
|
-
options[:incremental] = incremental
|
43
|
-
end
|
44
34
|
end
|
45
35
|
|
46
36
|
# Parse the options and return if we don't have any target.
|
@@ -50,28 +40,8 @@ module VagrantReflect
|
|
50
40
|
# Build up the paths that we need to listen to.
|
51
41
|
paths = {}
|
52
42
|
with_target_vms(argv) do |machine|
|
53
|
-
|
54
|
-
|
55
|
-
if proxy
|
56
|
-
machine.ui.warn(
|
57
|
-
I18n.t(
|
58
|
-
'vagrant.plugins.vagrant-reflect.rsync_proxy_machine',
|
59
|
-
name: machine.name.to_s,
|
60
|
-
provider: machine.provider_name.to_s))
|
61
|
-
|
62
|
-
machine = proxy
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
cached = synced_folders(machine, cached: true)
|
67
|
-
fresh = synced_folders(machine)
|
68
|
-
diff = synced_folders_diff(cached, fresh)
|
69
|
-
unless diff[:added].empty?
|
70
|
-
machine.ui.warn(
|
71
|
-
I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_new_folders'))
|
72
|
-
end
|
73
|
-
|
74
|
-
folders = cached[:rsync]
|
43
|
+
machine = check_proxy(machine)
|
44
|
+
folders = get_folders(machine)
|
75
45
|
next if !folders || folders.empty?
|
76
46
|
|
77
47
|
folders.each do |id, folder_opts|
|
@@ -84,7 +54,7 @@ module VagrantReflect
|
|
84
54
|
folder_opts[:exclude] ||= []
|
85
55
|
folder_opts[:exclude] << '.vagrant/'
|
86
56
|
|
87
|
-
syncer =
|
57
|
+
syncer = Util::Sync.new(machine, folder_opts)
|
88
58
|
|
89
59
|
machine.ui.info(
|
90
60
|
I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_initial'))
|
@@ -120,7 +90,7 @@ module VagrantReflect
|
|
120
90
|
|
121
91
|
ignores = []
|
122
92
|
opts.each do |path_opts|
|
123
|
-
ignores += path_opts[:
|
93
|
+
ignores += Util::Excludes.convert(path_opts[:opts][:excludes] || [])
|
124
94
|
path_opts[:machine].ui.info(
|
125
95
|
I18n.t(
|
126
96
|
'vagrant.plugins.vagrant-reflect.rsync_auto_path',
|
@@ -158,6 +128,33 @@ module VagrantReflect
|
|
158
128
|
0
|
159
129
|
end
|
160
130
|
|
131
|
+
def check_proxy(machine)
|
132
|
+
return machine unless machine.provider.capability?(:proxy_machine)
|
133
|
+
|
134
|
+
proxy = machine.provider.capability(:proxy_machine)
|
135
|
+
return machine unless proxy
|
136
|
+
|
137
|
+
machine.ui.warn(
|
138
|
+
I18n.t(
|
139
|
+
'vagrant.plugins.vagrant-reflect.rsync_proxy_machine',
|
140
|
+
name: machine.name.to_s,
|
141
|
+
provider: machine.provider_name.to_s))
|
142
|
+
|
143
|
+
proxy
|
144
|
+
end
|
145
|
+
|
146
|
+
def get_folders(machine)
|
147
|
+
cached = synced_folders(machine, cached: true)
|
148
|
+
fresh = synced_folders(machine)
|
149
|
+
diff = synced_folders_diff(cached, fresh)
|
150
|
+
unless diff[:added].empty?
|
151
|
+
machine.ui.warn(
|
152
|
+
I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_new_folders'))
|
153
|
+
end
|
154
|
+
|
155
|
+
cached[:rsync]
|
156
|
+
end
|
157
|
+
|
161
158
|
# This is the callback that is called when any changes happen
|
162
159
|
def callback(path, opts, options, modified, added, removed)
|
163
160
|
@logger.info("File change callback called for #{path}!")
|
@@ -174,6 +171,7 @@ module VagrantReflect
|
|
174
171
|
send callback, path, path_opts, modified, added, removed
|
175
172
|
|
176
173
|
path_opts[:machine].ui.info(
|
174
|
+
get_sync_time +
|
177
175
|
I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_synced'))
|
178
176
|
rescue Vagrant::Errors::MachineGuestNotReady
|
179
177
|
# Error communicating to the machine, probably a reload or
|
@@ -209,12 +207,15 @@ module VagrantReflect
|
|
209
207
|
end
|
210
208
|
|
211
209
|
def sync_incremental(path, path_opts, modified, added, removed)
|
210
|
+
sync_time = get_sync_time
|
211
|
+
|
212
212
|
if !modified.empty? || !added.empty?
|
213
213
|
# Pass the list of changes to rsync so we quickly synchronise only
|
214
214
|
# the changed files instead of the whole folder
|
215
215
|
items = strip_paths(path, modified + added)
|
216
216
|
path_opts[:syncer].sync_incremental(items) do |item|
|
217
217
|
path_opts[:machine].ui.info(
|
218
|
+
sync_time +
|
218
219
|
I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_increment_change',
|
219
220
|
path: item))
|
220
221
|
end
|
@@ -226,11 +227,23 @@ module VagrantReflect
|
|
226
227
|
items = strip_paths(path, removed)
|
227
228
|
path_opts[:syncer].sync_removals(items) do |item|
|
228
229
|
path_opts[:machine].ui.info(
|
230
|
+
sync_time +
|
229
231
|
I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_increment_remove',
|
230
232
|
path: item))
|
231
233
|
end
|
232
234
|
end
|
233
235
|
|
236
|
+
def get_sync_time()
|
237
|
+
# TODO: Hold this configuration per machine when we refactor
|
238
|
+
with_target_vms(nil, single_target: true) do |vm|
|
239
|
+
if vm.config.reflect.show_sync_time == true
|
240
|
+
return '(' + Time.now.strftime("%H:%M:%S") + ') '
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
''
|
245
|
+
end
|
246
|
+
|
234
247
|
def strip_paths(path, items)
|
235
248
|
items.map do |item|
|
236
249
|
item[path.length..-1]
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module VagrantReflect
|
2
|
+
module Configuration
|
3
|
+
# Configuration object for vagrant-reflect
|
4
|
+
class Reflect < Vagrant.plugin('2', :config)
|
5
|
+
attr_accessor :show_sync_time
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@show_sync_time = UNSET_VALUE
|
9
|
+
end
|
10
|
+
|
11
|
+
def finalize!
|
12
|
+
@show_sync_time = 0 if @show_sync_time == UNSET_VALUE
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate(_)
|
16
|
+
errors = _detected_errors
|
17
|
+
if show_sync_time != true && show_sync_time != false
|
18
|
+
errors << 'show_sync_time must be TRUE or FALSE'
|
19
|
+
end
|
20
|
+
|
21
|
+
{ 'reflect' => errors }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module VagrantReflect
|
2
|
+
module Util
|
3
|
+
# This is a helper that builds the required commands and returns them
|
4
|
+
class Excludes
|
5
|
+
class << self
|
6
|
+
PATTERNS = [
|
7
|
+
['.', '\\.'],
|
8
|
+
['***', '|||EMPTY|||'],
|
9
|
+
['**', '|||GLOBAL|||'],
|
10
|
+
['*', '|||PATH|||'],
|
11
|
+
['?', '[^/]'],
|
12
|
+
['|||PATH|||', '[^/]+'],
|
13
|
+
['|||GLOBAL|||', '.+'],
|
14
|
+
['|||EMPTY|||', '.*']
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
# This converts the rsync exclude patterns to regular expressions we can
|
18
|
+
# send to Listen.
|
19
|
+
def convert(excludes)
|
20
|
+
excludes.map(&method(:convert_single))
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def convert_single(exclude)
|
26
|
+
start_anchor = false
|
27
|
+
|
28
|
+
if exclude.start_with?('/')
|
29
|
+
start_anchor = true
|
30
|
+
exclude = exclude[1..-1]
|
31
|
+
end
|
32
|
+
|
33
|
+
regexp = start_anchor ? '^' : '(?:^|/)'
|
34
|
+
regexp += perform_substitutions(exclude)
|
35
|
+
regexp += exclude.end_with?('/') ? '' : '(?:/|$)'
|
36
|
+
|
37
|
+
Regexp.new(regexp)
|
38
|
+
end
|
39
|
+
|
40
|
+
def perform_substitutions(exclude)
|
41
|
+
# This is REALLY ghetto, but its a start. We can improve and
|
42
|
+
# keep unit tests passing in the future.
|
43
|
+
# TODO: Escaped wildcards get substituted incorrectly;
|
44
|
+
# replace with FSM?
|
45
|
+
PATTERNS.each do |pattern|
|
46
|
+
exclude = exclude.gsub(pattern[0], pattern[1])
|
47
|
+
end
|
48
|
+
exclude
|
49
|
+
end
|
50
|
+
end # << self
|
51
|
+
end # ::Excludes
|
52
|
+
end # ::Util
|
53
|
+
end # ::VagrantReflect
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'driskell-listen'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# VagrantReflect module
|
5
|
+
module VagrantReflect
|
6
|
+
class ShutdownSignal < StandardError; end
|
7
|
+
|
8
|
+
# Reflector instance
|
9
|
+
class Reflector
|
10
|
+
SIGNALS = %w(TERM INT).freeze
|
11
|
+
|
12
|
+
def initialize(path)
|
13
|
+
@path = Pathname.new(File.expand_path(path))
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
setup_signals
|
18
|
+
start
|
19
|
+
|
20
|
+
@thread = Thread.current
|
21
|
+
Thread.stop
|
22
|
+
rescue ShutdownSignal
|
23
|
+
stop
|
24
|
+
shutdown_signals
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def setup_signals
|
30
|
+
each_signal do
|
31
|
+
raise ShutdownSignal
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def shutdown_signals
|
36
|
+
each_signal 'DEFAULT'
|
37
|
+
end
|
38
|
+
|
39
|
+
def each_signal(command = nil, &block)
|
40
|
+
SIGNALS.each do |signal|
|
41
|
+
Signal.trap signal, block_given? ? block : command
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def start
|
46
|
+
@listener = Driskell::Listen.to(@path, &method(:callback))
|
47
|
+
@listener.start
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop
|
51
|
+
@listener.stop if @listener.state != :stopped
|
52
|
+
end
|
53
|
+
|
54
|
+
def record
|
55
|
+
@record unless @record.nil?
|
56
|
+
|
57
|
+
# TODO: This is a hack to get at the record data, we need to expose it in
|
58
|
+
# the listen gem
|
59
|
+
backend = @listener.instance_variable_get(:@backend)
|
60
|
+
adapter = backend.instance_variable_get(:@adapter)
|
61
|
+
change = adapter.instance_variable_get(:@snapshots)[@path]
|
62
|
+
@record = change.instance_variable_get(:@record)
|
63
|
+
end
|
64
|
+
|
65
|
+
def callback(modified, added, removed)
|
66
|
+
output = {}
|
67
|
+
|
68
|
+
output[:added] = added.map(&method(:fetch_data))
|
69
|
+
output[:modified] = modified.map(&method(:fetch_data))
|
70
|
+
output[:removed] = removed
|
71
|
+
|
72
|
+
puts JSON.fast_generate(output)
|
73
|
+
end
|
74
|
+
|
75
|
+
def fetch_data(path)
|
76
|
+
rel_path = Pathname.new(path).relative_path_from(@path)
|
77
|
+
data = record.file_data(rel_path)
|
78
|
+
data[:path] = rel_path
|
79
|
+
data
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module VagrantReflect
|
2
|
+
module Util
|
3
|
+
# This is a helper that builds the required commands and returns them
|
4
|
+
class Shell
|
5
|
+
def initialize(machine, guestpath, hostpath, excludes)
|
6
|
+
@machine = machine
|
7
|
+
@guestpath = guestpath
|
8
|
+
@hostpath = hostpath
|
9
|
+
@excludes = excludes || []
|
10
|
+
|
11
|
+
init_connection_info
|
12
|
+
init_commands
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :rsync_command_inc
|
16
|
+
attr_reader :rsync_command_full
|
17
|
+
attr_reader :rm_command
|
18
|
+
attr_reader :rmdir_command
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def init_connection_info
|
23
|
+
# Connection information
|
24
|
+
username = @machine.ssh_info[:username]
|
25
|
+
host = @machine.ssh_info[:host]
|
26
|
+
@remote = "#{username}@#{host}"
|
27
|
+
@target = "#{@remote}:#{@guestpath}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def init_commands
|
31
|
+
@workdir = @machine.env.root_path.to_s
|
32
|
+
|
33
|
+
build_rsh_command
|
34
|
+
|
35
|
+
base = compile_base_rsync
|
36
|
+
build_rsync_command_full base
|
37
|
+
build_rsync_command_inc base
|
38
|
+
|
39
|
+
build_rm_command
|
40
|
+
build_rmdir_command
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_rsh_command
|
44
|
+
proxy_command = []
|
45
|
+
if @machine.ssh_info[:proxy_command]
|
46
|
+
proxy_command += [
|
47
|
+
'-o',
|
48
|
+
"ProxyCommand='#{@machine.ssh_info[:proxy_command]}'"
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
@rsh = rsh_command_args(proxy_command).flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
def rsh_command_args(proxy_command)
|
56
|
+
[
|
57
|
+
'ssh',
|
58
|
+
'-p', @machine.ssh_info[:port].to_s,
|
59
|
+
proxy_command,
|
60
|
+
'-o', 'StrictHostKeyChecking=no',
|
61
|
+
'-o', 'IdentitiesOnly=true',
|
62
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
63
|
+
@machine.ssh_info[:private_key_path].map { |p| ['-i', p] }
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
def compile_base_rsync
|
68
|
+
# Get the command-line arguments
|
69
|
+
# TODO: Re-enable customisation of this
|
70
|
+
base_rsync = [
|
71
|
+
'rsync', '--verbose', '--archive', '--delete', '-z', '--links']
|
72
|
+
|
73
|
+
# On Windows, we have to set a default chmod flag to avoid permission
|
74
|
+
# issues
|
75
|
+
add_windows_chmod_args(base_rsync) if Vagrant::Util::Platform.windows?
|
76
|
+
|
77
|
+
add_owner_args(base_rsync)
|
78
|
+
|
79
|
+
base_rsync += ['-e', @rsh.join(' ')]
|
80
|
+
|
81
|
+
@excludes.map { |e| base_rsync += ['--exclude', e] }
|
82
|
+
|
83
|
+
base_rsync
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_windows_chmod_args(base_rsync)
|
87
|
+
return if base_rsync.any? { |arg| arg.start_with?('--chmod=') }
|
88
|
+
|
89
|
+
# Ensures that all non-masked bits get enabled
|
90
|
+
base_rsync << '--chmod=ugo=rwX'
|
91
|
+
|
92
|
+
# Remove the -p option if --archive is enabled (--archive equals
|
93
|
+
# -rlptgoD) otherwise new files will not have the destination-default
|
94
|
+
# permissions
|
95
|
+
return unless base_rsync.include?('--archive') ||
|
96
|
+
base_rsync.include?('-a')
|
97
|
+
|
98
|
+
base_rsync << '--no-perms'
|
99
|
+
end
|
100
|
+
|
101
|
+
def add_owner_args(base_rsync)
|
102
|
+
# Disable rsync's owner/group preservation (implied by --archive) unless
|
103
|
+
# specifically requested, since we adjust owner/group to match shared
|
104
|
+
# folder setting ourselves.
|
105
|
+
base_rsync << '--no-owner' unless
|
106
|
+
base_rsync.include?('--owner') || base_rsync.include?('-o')
|
107
|
+
base_rsync << '--no-group' unless
|
108
|
+
base_rsync.include?('--group') || base_rsync.include?('-g')
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_rsync_command_full(base_rsync)
|
112
|
+
@rsync_command_full = base_rsync + [
|
113
|
+
@hostpath, @target, { workdir: @workdir }]
|
114
|
+
end
|
115
|
+
|
116
|
+
def build_rsync_command_inc(base_rsync)
|
117
|
+
@rsync_command_inc = base_rsync + [
|
118
|
+
'--files-from=-', @hostpath, @target,
|
119
|
+
{ workdir: @workdir, notify: :stdin }]
|
120
|
+
end
|
121
|
+
|
122
|
+
def build_rm_command
|
123
|
+
@rm_command = @rsh + [
|
124
|
+
@remote, 'xargs rm -f', { workdir: @workdir, notify: :stdin }]
|
125
|
+
end
|
126
|
+
|
127
|
+
def build_rmdir_command
|
128
|
+
# Make this command silent
|
129
|
+
# Sometimes we attempt to remove parent folders that aren't empty yet
|
130
|
+
# on the remote because we didn't yet sync across all of the removals
|
131
|
+
@rmdir_command = @rsh + [@remote, 'xargs -n 1 rmdir 2>/dev/null',
|
132
|
+
{ workdir: @workdir, notify: :stdin }]
|
133
|
+
end
|
134
|
+
end # ::Shell
|
135
|
+
end # ::Util
|
136
|
+
end # ::VagrantReflect
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'vagrant/util/platform'
|
2
|
+
|
3
|
+
require_relative '../patched/subprocess'
|
4
|
+
require_relative 'shell'
|
5
|
+
|
6
|
+
module VagrantReflect
|
7
|
+
module Util
|
8
|
+
# This is a helper that abstracts out the functionality of rsync and rm
|
9
|
+
class Sync
|
10
|
+
def initialize(machine, opts)
|
11
|
+
@machine = machine
|
12
|
+
|
13
|
+
init_paths(opts)
|
14
|
+
|
15
|
+
@shell = Shell.new(@machine, @guestpath, @hostpath, opts[:excludes])
|
16
|
+
|
17
|
+
log_configuration opts[:excludes] || []
|
18
|
+
end
|
19
|
+
|
20
|
+
def sync_incremental(items, &block)
|
21
|
+
send_items_to_command items, @shell.rsync_command_inc, &block
|
22
|
+
end
|
23
|
+
|
24
|
+
def sync_full
|
25
|
+
r = Vagrant::Util::SubprocessPatched.execute(*@shell.rsync_command_full)
|
26
|
+
check_exit @shell.rsync_command_full, r
|
27
|
+
end
|
28
|
+
|
29
|
+
def sync_removals(items, &block)
|
30
|
+
# Look for removed directories and fill in guest paths
|
31
|
+
dirs = prepare_items_for_removal(items)
|
32
|
+
|
33
|
+
send_items_to_command items, @shell.rm_command, &block
|
34
|
+
sync_removals_parents dirs.values unless dirs.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def sync_removals_parents(guest_items)
|
38
|
+
send_items_to_command guest_items, @shell.rmdir_command
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def log_configuration(excludes)
|
44
|
+
@machine.ui.info(
|
45
|
+
I18n.t(
|
46
|
+
'vagrant.plugins.vagrant-reflect.rsync_folder_configuration',
|
47
|
+
guestpath: @guestpath,
|
48
|
+
hostpath: @hostpath))
|
49
|
+
|
50
|
+
return if excludes.empty?
|
51
|
+
|
52
|
+
@machine.ui.info(
|
53
|
+
I18n.t(
|
54
|
+
'vagrant.plugins.vagrant-reflect.rsync_folder_excludes',
|
55
|
+
excludes: excludes.inspect))
|
56
|
+
end
|
57
|
+
|
58
|
+
def init_paths(opts)
|
59
|
+
# Folder info
|
60
|
+
@guestpath = opts[:guestpath]
|
61
|
+
@hostpath = opts[:hostpath]
|
62
|
+
@hostpath = File.expand_path(@hostpath, @machine.env.root_path)
|
63
|
+
@hostpath = Vagrant::Util::Platform.fs_real_path(@hostpath).to_s
|
64
|
+
|
65
|
+
if Vagrant::Util::Platform.windows?
|
66
|
+
# rsync for Windows expects cygwin style paths, always.
|
67
|
+
@hostpath = Vagrant::Util::Platform.cygwin_path(@hostpath)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Make sure the host path ends with a "/" to avoid creating
|
71
|
+
# a nested directory...
|
72
|
+
@hostpath += '/' unless @hostpath.end_with?('/')
|
73
|
+
end
|
74
|
+
|
75
|
+
def prepare_items_for_removal(items)
|
76
|
+
dirs = {}
|
77
|
+
items.map! do |rel_path|
|
78
|
+
check_for_empty_parents(rel_path, dirs)
|
79
|
+
@guestpath + rel_path
|
80
|
+
end
|
81
|
+
dirs
|
82
|
+
end
|
83
|
+
|
84
|
+
def check_for_empty_parents(rel_path, dirs)
|
85
|
+
parent = rel_path
|
86
|
+
loop do
|
87
|
+
parent = File.dirname(parent)
|
88
|
+
break if parent == '/'
|
89
|
+
next if File.exist?(@hostpath + parent)
|
90
|
+
# Insertion order is maintained so ensure we move repeated paths to
|
91
|
+
# end so they are deleted last
|
92
|
+
dirs.delete parent
|
93
|
+
dirs[parent] = @guestpath + parent
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def send_items_to_command(items, command, &block)
|
98
|
+
current = next_item(items, &block)
|
99
|
+
r = Vagrant::Util::SubprocessPatched.execute(*command) do |what, io|
|
100
|
+
next if what != :stdin
|
101
|
+
|
102
|
+
current = process_items(io, items, current, &block)
|
103
|
+
end
|
104
|
+
check_exit command, r
|
105
|
+
end
|
106
|
+
|
107
|
+
def process_items(io, items, current, &block)
|
108
|
+
until current.nil?
|
109
|
+
send_data(io, current)
|
110
|
+
current = next_item(items, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Finished! Close stdin
|
114
|
+
io.close_write
|
115
|
+
rescue IO::WaitWritable, Errno::EINTR
|
116
|
+
# Wait for writable again
|
117
|
+
return current
|
118
|
+
end
|
119
|
+
|
120
|
+
def next_item(items)
|
121
|
+
return nil if items.empty?
|
122
|
+
current = items.shift + "\n"
|
123
|
+
yield current if block_given?
|
124
|
+
current
|
125
|
+
end
|
126
|
+
|
127
|
+
def send_data(io, current)
|
128
|
+
# Handle partial writes
|
129
|
+
n = io.write_nonblock(current)
|
130
|
+
return unless n < current.length
|
131
|
+
current.slice! 0, n
|
132
|
+
throw IO::WaitWritable
|
133
|
+
end
|
134
|
+
|
135
|
+
def check_exit(command, r)
|
136
|
+
return if r.exit_code == 0
|
137
|
+
|
138
|
+
raise Vagrant::Errors::RSyncError,
|
139
|
+
command: command.join(' '),
|
140
|
+
guestpath: @guestpath,
|
141
|
+
hostpath: @hostpath,
|
142
|
+
stderr: r.stderr
|
143
|
+
end
|
144
|
+
end # ::Sync
|
145
|
+
end # ::Util
|
146
|
+
end # ::VagrantReflect
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vagrant-reflect
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Woods
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: driskell-listen
|
@@ -39,9 +39,13 @@ extra_rdoc_files: []
|
|
39
39
|
files:
|
40
40
|
- lib/vagrant-reflect.rb
|
41
41
|
- lib/vagrant-reflect/command/reflect.rb
|
42
|
+
- lib/vagrant-reflect/configuration/reflect.rb
|
42
43
|
- lib/vagrant-reflect/patched/subprocess.rb
|
43
44
|
- lib/vagrant-reflect/plugin.rb
|
44
|
-
- lib/vagrant-reflect/util/
|
45
|
+
- lib/vagrant-reflect/util/excludes.rb
|
46
|
+
- lib/vagrant-reflect/util/reflector.rb
|
47
|
+
- lib/vagrant-reflect/util/shell.rb
|
48
|
+
- lib/vagrant-reflect/util/sync.rb
|
45
49
|
- lib/vagrant-reflect/version.rb
|
46
50
|
- templates/locales/en.yml
|
47
51
|
homepage: https://github.com/driskell/vagrant-reflect
|
@@ -1,327 +0,0 @@
|
|
1
|
-
require 'vagrant/util/platform'
|
2
|
-
|
3
|
-
require_relative '../patched/subprocess'
|
4
|
-
|
5
|
-
module VagrantReflect
|
6
|
-
# This is a helper that abstracts out the functionality of rsyncing
|
7
|
-
# folders so that it can be called from anywhere.
|
8
|
-
class Syncer
|
9
|
-
RSYNC_TO_REGEXP_PATTERNS = [
|
10
|
-
['.', '\\.'],
|
11
|
-
['***', '|||EMPTY|||'],
|
12
|
-
['**', '|||GLOBAL|||'],
|
13
|
-
['*', '|||PATH|||'],
|
14
|
-
['?', '[^/]'],
|
15
|
-
['|||PATH|||', '[^/]+'],
|
16
|
-
['|||GLOBAL|||', '.+'],
|
17
|
-
['|||EMPTY|||', '.*']
|
18
|
-
].freeze
|
19
|
-
|
20
|
-
def initialize(machine, opts)
|
21
|
-
@opts = opts
|
22
|
-
@machine = machine
|
23
|
-
@workdir = @machine.env.root_path.to_s
|
24
|
-
|
25
|
-
init_paths
|
26
|
-
init_connection_info
|
27
|
-
init_excludes
|
28
|
-
init_commands
|
29
|
-
end
|
30
|
-
|
31
|
-
def log_configuration
|
32
|
-
@machine.ui.info(
|
33
|
-
I18n.t(
|
34
|
-
'vagrant.plugins.vagrant-reflect.rsync_folder_configuration',
|
35
|
-
guestpath: @guestpath,
|
36
|
-
hostpath: @hostpath))
|
37
|
-
|
38
|
-
return if @excludes.empty?
|
39
|
-
|
40
|
-
@machine.ui.info(
|
41
|
-
I18n.t(
|
42
|
-
'vagrant.plugins.vagrant-reflect.rsync_folder_excludes',
|
43
|
-
excludes: @excludes.inspect))
|
44
|
-
end
|
45
|
-
|
46
|
-
# This converts the rsync exclude patterns to regular expressions we can
|
47
|
-
# send to Listen.
|
48
|
-
def excludes_to_regexp
|
49
|
-
return @regexp if @regexp
|
50
|
-
|
51
|
-
@regexp = @excludes.map(&method(:exclude_to_regex_single))
|
52
|
-
end
|
53
|
-
|
54
|
-
def sync_incremental(items, &block)
|
55
|
-
send_items_to_command items, @rsync_command_inc, &block
|
56
|
-
end
|
57
|
-
|
58
|
-
def sync_full
|
59
|
-
r = Vagrant::Util::SubprocessPatched.execute(*@rsync_command_full)
|
60
|
-
check_exit @rsync_command_full, r
|
61
|
-
end
|
62
|
-
|
63
|
-
def sync_removals(items, &block)
|
64
|
-
# Look for removed directories and fill in guest paths
|
65
|
-
dirs = prepare_items_for_removal(items)
|
66
|
-
|
67
|
-
send_items_to_command items, @rm_command, &block
|
68
|
-
sync_removals_parents dirs.values, &block unless dirs.empty?
|
69
|
-
end
|
70
|
-
|
71
|
-
def sync_removals_parents(guest_items, &block)
|
72
|
-
send_items_to_command guest_items, @rmdir_command, &block
|
73
|
-
end
|
74
|
-
|
75
|
-
protected
|
76
|
-
|
77
|
-
def init_paths
|
78
|
-
# Folder info
|
79
|
-
@guestpath = @opts[:guestpath]
|
80
|
-
@hostpath = @opts[:hostpath]
|
81
|
-
@hostpath = File.expand_path(@hostpath, @machine.env.root_path)
|
82
|
-
@hostpath = Vagrant::Util::Platform.fs_real_path(@hostpath).to_s
|
83
|
-
|
84
|
-
if Vagrant::Util::Platform.windows?
|
85
|
-
# rsync for Windows expects cygwin style paths, always.
|
86
|
-
@hostpath = Vagrant::Util::Platform.cygwin_path(@hostpath)
|
87
|
-
end
|
88
|
-
|
89
|
-
# Make sure the host path ends with a "/" to avoid creating
|
90
|
-
# a nested directory...
|
91
|
-
@hostpath += '/' unless @hostpath.end_with?('/')
|
92
|
-
end
|
93
|
-
|
94
|
-
def init_connection_info
|
95
|
-
# Connection information
|
96
|
-
username = @machine.ssh_info[:username]
|
97
|
-
host = @machine.ssh_info[:host]
|
98
|
-
@remote = "#{username}@#{host}"
|
99
|
-
@target = "#{@remote}:#{@guestpath}"
|
100
|
-
end
|
101
|
-
|
102
|
-
def init_excludes
|
103
|
-
# Exclude some files by default, and any that might be configured
|
104
|
-
# by the user.
|
105
|
-
@excludes = []
|
106
|
-
@excludes += Array(@opts[:exclude]).map(&:to_s) if @opts[:exclude]
|
107
|
-
@excludes.uniq!
|
108
|
-
end
|
109
|
-
|
110
|
-
def init_commands
|
111
|
-
init_rsh_command
|
112
|
-
init_rsync_command
|
113
|
-
init_rsync_command_full
|
114
|
-
init_rsync_command_inc
|
115
|
-
init_rm_command
|
116
|
-
init_rmdir_command
|
117
|
-
end
|
118
|
-
|
119
|
-
def init_rsh_command
|
120
|
-
proxy_command = []
|
121
|
-
if @machine.ssh_info[:proxy_command]
|
122
|
-
proxy_command += [
|
123
|
-
'-o',
|
124
|
-
"ProxyCommand='#{@machine.ssh_info[:proxy_command]}'"
|
125
|
-
]
|
126
|
-
end
|
127
|
-
|
128
|
-
@rsh = build_rsh_command(proxy_command).flatten
|
129
|
-
end
|
130
|
-
|
131
|
-
def build_rsh_command(proxy_command)
|
132
|
-
[
|
133
|
-
'ssh',
|
134
|
-
'-p', @machine.ssh_info[:port].to_s,
|
135
|
-
proxy_command,
|
136
|
-
'-o', 'StrictHostKeyChecking=no',
|
137
|
-
'-o', 'IdentitiesOnly=true',
|
138
|
-
'-o', 'UserKnownHostsFile=/dev/null',
|
139
|
-
@machine.ssh_info[:private_key_path].map { |p| ['-i', p] }
|
140
|
-
]
|
141
|
-
end
|
142
|
-
|
143
|
-
def init_rsync_command
|
144
|
-
# Get the command-line arguments
|
145
|
-
if @opts[:args]
|
146
|
-
@rsync_command = ['rsync'] + Array(@opts[:args]).dup
|
147
|
-
else
|
148
|
-
@rsync_command = [
|
149
|
-
'rsync', '--verbose', '--archive', '--delete', '-z', '--copy-links']
|
150
|
-
end
|
151
|
-
|
152
|
-
# On Windows, we have to set a default chmod flag to avoid permission
|
153
|
-
# issues
|
154
|
-
build_windows_chmod_args if Vagrant::Util::Platform.windows?
|
155
|
-
|
156
|
-
build_owner_args
|
157
|
-
|
158
|
-
@rsync_command += ['-e', @rsh.join(' ')]
|
159
|
-
|
160
|
-
@excludes.map { |e| @rsync_command += ['--exclude', e] }
|
161
|
-
end
|
162
|
-
|
163
|
-
def build_windows_chmod_args
|
164
|
-
return if @rsync_command.any? { |arg| arg.start_with?('--chmod=') }
|
165
|
-
|
166
|
-
# Ensures that all non-masked bits get enabled
|
167
|
-
@rsync_command << '--chmod=ugo=rwX'
|
168
|
-
|
169
|
-
# Remove the -p option if --archive is enabled (--archive equals
|
170
|
-
# -rlptgoD) otherwise new files will not have the destination-default
|
171
|
-
# permissions
|
172
|
-
return unless @rsync_command.include?('--archive') ||
|
173
|
-
@rsync_command.include?('-a')
|
174
|
-
|
175
|
-
@rsync_command << '--no-perms'
|
176
|
-
end
|
177
|
-
|
178
|
-
def build_owner_args
|
179
|
-
# Disable rsync's owner/group preservation (implied by --archive) unless
|
180
|
-
# specifically requested, since we adjust owner/group to match shared
|
181
|
-
# folder setting ourselves.
|
182
|
-
@rsync_command << '--no-owner' unless
|
183
|
-
@rsync_command.include?('--owner') || @rsync_command.include?('-o')
|
184
|
-
@rsync_command << '--no-group' unless
|
185
|
-
@rsync_command.include?('--group') || @rsync_command.include?('-g')
|
186
|
-
end
|
187
|
-
|
188
|
-
def init_rsync_command_full
|
189
|
-
@rsync_command_full = @rsync_command.dup + [
|
190
|
-
@hostpath,
|
191
|
-
@target,
|
192
|
-
{
|
193
|
-
workdir: @workdir
|
194
|
-
}
|
195
|
-
]
|
196
|
-
end
|
197
|
-
|
198
|
-
def init_rsync_command_inc
|
199
|
-
@rsync_command_inc = @rsync_command.dup + [
|
200
|
-
'--files-from=-',
|
201
|
-
@hostpath,
|
202
|
-
@target,
|
203
|
-
{
|
204
|
-
workdir: @workdir,
|
205
|
-
notify: :stdin
|
206
|
-
}
|
207
|
-
]
|
208
|
-
end
|
209
|
-
|
210
|
-
def init_rm_command
|
211
|
-
@rm_command = @rsh.dup + [
|
212
|
-
@remote,
|
213
|
-
'xargs rm -f',
|
214
|
-
{
|
215
|
-
workdir: @workdir,
|
216
|
-
notify: :stdin
|
217
|
-
}
|
218
|
-
]
|
219
|
-
end
|
220
|
-
|
221
|
-
def init_rmdir_command
|
222
|
-
@rmdir_command = @rsh.dup + [
|
223
|
-
@remote,
|
224
|
-
'xargs rmdir',
|
225
|
-
{
|
226
|
-
workdir: @workdir,
|
227
|
-
notify: :stdin
|
228
|
-
}
|
229
|
-
]
|
230
|
-
end
|
231
|
-
|
232
|
-
def prepare_items_for_removal(items)
|
233
|
-
dirs = {}
|
234
|
-
items.map! do |rel_path|
|
235
|
-
check_for_empty_parents(rel_path, dirs)
|
236
|
-
@guestpath + rel_path
|
237
|
-
end
|
238
|
-
dirs
|
239
|
-
end
|
240
|
-
|
241
|
-
def check_for_empty_parents(rel_path, dirs)
|
242
|
-
parent = rel_path
|
243
|
-
loop do
|
244
|
-
parent = File.dirname(parent)
|
245
|
-
break if parent == '/'
|
246
|
-
next if File.exist?(@hostpath + parent)
|
247
|
-
# Insertion order is maintained so ensure we move repeated paths to
|
248
|
-
# end so they are deleted last
|
249
|
-
dirs.delete parent
|
250
|
-
dirs[parent] = @guestpath + parent
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
def send_items_to_command(items, command, &block)
|
255
|
-
current = next_item(items, &block)
|
256
|
-
r = Vagrant::Util::SubprocessPatched.execute(*command) do |what, io|
|
257
|
-
next if what != :stdin
|
258
|
-
|
259
|
-
current = process_items(io, items, current, &block)
|
260
|
-
end
|
261
|
-
check_exit command, r
|
262
|
-
end
|
263
|
-
|
264
|
-
def process_items(io, items, current, &block)
|
265
|
-
until current.nil?
|
266
|
-
send_data(io, current)
|
267
|
-
current = next_item(items, &block)
|
268
|
-
end
|
269
|
-
|
270
|
-
# Finished! Close stdin
|
271
|
-
io.close_write
|
272
|
-
rescue IO::WaitWritable, Errno::EINTR
|
273
|
-
# Wait for writable again
|
274
|
-
return current
|
275
|
-
end
|
276
|
-
|
277
|
-
def next_item(items)
|
278
|
-
return nil if items.empty?
|
279
|
-
current = items.shift + "\n"
|
280
|
-
yield current if block_given?
|
281
|
-
current
|
282
|
-
end
|
283
|
-
|
284
|
-
def send_data(io, current)
|
285
|
-
# Handle partial writes
|
286
|
-
n = io.write_nonblock(current)
|
287
|
-
return unless n < current.length
|
288
|
-
current.slice! 0, n
|
289
|
-
throw IO::WaitWritable
|
290
|
-
end
|
291
|
-
|
292
|
-
def check_exit(command, r)
|
293
|
-
return if r.exit_code == 0
|
294
|
-
|
295
|
-
raise Vagrant::Errors::RSyncError,
|
296
|
-
command: command.join(' '),
|
297
|
-
guestpath: @guestpath,
|
298
|
-
hostpath: @hostpath,
|
299
|
-
stderr: r.stderr
|
300
|
-
end
|
301
|
-
|
302
|
-
def exclude_to_regex_single(exclude)
|
303
|
-
start_anchor = false
|
304
|
-
|
305
|
-
if exclude.start_with?('/')
|
306
|
-
start_anchor = true
|
307
|
-
exclude = exclude[1..-1]
|
308
|
-
end
|
309
|
-
|
310
|
-
regexp = start_anchor ? '^' : '(?:^|/)'
|
311
|
-
regexp += perform_substitutions(exclude)
|
312
|
-
regexp += exclude.end_with?('/') ? '' : '(?:/|$)'
|
313
|
-
|
314
|
-
Regexp.new(regexp)
|
315
|
-
end
|
316
|
-
|
317
|
-
def perform_substitutions(exclude)
|
318
|
-
# This is REALLY ghetto, but its a start. We can improve and
|
319
|
-
# keep unit tests passing in the future.
|
320
|
-
# TODO: Escaped wildcards get substituted incorrectly - replace with FSM?
|
321
|
-
RSYNC_TO_REGEXP_PATTERNS.each do |pattern|
|
322
|
-
exclude = exclude.gsub(pattern[0], pattern[1])
|
323
|
-
end
|
324
|
-
exclude
|
325
|
-
end
|
326
|
-
end
|
327
|
-
end
|