vagrant-reflect 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|