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 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