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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 27514930e4a083e72bf754603e5cf8b1a4e4edfe
4
- data.tar.gz: 143c954865bc3b8d4292eba8fd13e87f8582a70a
3
+ metadata.gz: e84bae9f41ff115636ac176da7601c803d83389d
4
+ data.tar.gz: 627b6a9da251a1da3998de39a25b6fd82564b9a2
5
5
  SHA512:
6
- metadata.gz: 292239d704ae4416df60d289d695456b7d5ae1bf71c743572b78e9a046dabc673d12cf7880443c5a76ea8cfecabb3fb0eb637169d88097f4e56ea951acb5d4a3
7
- data.tar.gz: 378cfc2e9047b6fa9f3b2b9378e7d9c42b94ffc34beb2326962a5d4257fb53ded9a87428978f0875dab365c1d0f49059b7a5a6e01bd44172c25dfdc5e7c28f41
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/syncer'
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
- if machine.provider.capability?(:proxy_machine)
54
- proxy = machine.provider.capability(:proxy_machine)
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 = Syncer.new(machine, folder_opts)
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[:syncer].excludes_to_regexp
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
@@ -1,3 +1,6 @@
1
+ # THIS IS A PATCHED VERSION OF Vagrant::Util::Subprocess THAT FIXES THE ISSUE
2
+ # REPORTED AT: https://github.com/mitchellh/vagrant/pull/7192
3
+
1
4
  require 'thread'
2
5
 
3
6
  require 'childprocess'
@@ -8,5 +8,10 @@ module VagrantReflect
8
8
  require_relative 'command/reflect'
9
9
  Command::Reflect
10
10
  end
11
+
12
+ config 'reflect' do
13
+ require_relative 'configuration/reflect'
14
+ Configuration::Reflect
15
+ end
11
16
  end
12
17
  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
@@ -1,3 +1,3 @@
1
1
  module VagrantReflect
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
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.6.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-03-31 00:00:00.000000000 Z
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/syncer.rb
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