vagrant-reflect 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 60e8d10dfb9b80d4280d579c126cd27ca7fdc7c0
4
+ data.tar.gz: 24af14e99f05a3fc076016b46289d6b428bee72c
5
+ SHA512:
6
+ metadata.gz: d6715d8afd918ac9f9d785b7fa4efe8317afabd485f018852685a51a7e08095c59bb4da540bd2fa53fcfceea68c657d63ec647cf6016636a7246c4f37ca088b5
7
+ data.tar.gz: 89a8795fa57b45c46fd80034a38be6493e40a3eac3446869f9ed457d4ec923e0b8c865f2cbc6fffdf8bf35f6da0b05f8e4338fca3f2883b1f9a0fd4c56e314a3
@@ -0,0 +1,285 @@
1
+ require 'log4r'
2
+ require 'optparse'
3
+ require 'thread'
4
+
5
+ require 'vagrant/action/builtin/mixin_synced_folders'
6
+ require 'vagrant/util/busy'
7
+ require 'vagrant/util/platform'
8
+
9
+ require_relative '../helper'
10
+
11
+ require 'driskell-listen'
12
+
13
+ module VagrantReflect
14
+ module Command
15
+ class Reflect < Vagrant.plugin('2', :command)
16
+ include Vagrant::Action::Builtin::MixinSyncedFolders
17
+
18
+ def self.synopsis
19
+ 'a better rsync-auto'
20
+ end
21
+
22
+ def execute
23
+ @logger = Log4r::Logger.new('vagrant::commands::reflect')
24
+
25
+ options = {
26
+ poll: false,
27
+ incremental: true
28
+ }
29
+ opts = OptionParser.new do |o|
30
+ 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
+ end
45
+
46
+ # Parse the options and return if we don't have any target.
47
+ argv = parse_options(opts)
48
+ return unless argv
49
+
50
+ # Build up the paths that we need to listen to.
51
+ paths = {}
52
+ 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]
75
+ next if !folders || folders.empty?
76
+
77
+ # Get the SSH info for this machine so we can do an initial
78
+ # sync to the VM.
79
+ ssh_info = machine.ssh_info
80
+ if ssh_info
81
+ machine.ui.info(
82
+ I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_initial'))
83
+ folders.each do |_, folder_opts|
84
+ SyncHelper.sync_single(machine, ssh_info, folder_opts)
85
+ end
86
+ end
87
+
88
+ folders.each do |id, folder_opts|
89
+ # If we marked this folder to not auto sync, then
90
+ # don't do it.
91
+ next if folder_opts.key?(:auto) && !folder_opts[:auto]
92
+
93
+ hostpath = folder_opts[:hostpath]
94
+ hostpath = File.expand_path(hostpath, machine.env.root_path)
95
+ paths[hostpath] ||= []
96
+ paths[hostpath] << {
97
+ id: id,
98
+ machine: machine,
99
+ opts: folder_opts
100
+ }
101
+ end
102
+ end
103
+
104
+ # Exit immediately if there is nothing to watch
105
+ if paths.empty?
106
+ @env.ui.info(
107
+ I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_no_paths'))
108
+ return 1
109
+ end
110
+
111
+ @logger.info(
112
+ "Listening via: #{Driskell::Listen::Adapter.select.inspect}")
113
+
114
+ # Create a listener for each path so the callback can easily
115
+ # distinguish which path changed
116
+ listeners = paths.keys.sort.collect do |path|
117
+ opts = paths[path]
118
+ callback = method(:callback).to_proc.curry[path][opts][options]
119
+
120
+ ignores = []
121
+ opts.each do |path_opts|
122
+ path_opts[:machine].ui.info(
123
+ I18n.t(
124
+ 'vagrant.plugins.vagrant-reflect.rsync_auto_path',
125
+ path: path.to_s))
126
+
127
+ next unless path_opts[:exclude]
128
+
129
+ Array(path_opts[:exclude]).each do |pattern|
130
+ ignores << SyncHelper.exclude_to_regexp(hostpath, pattern.to_s)
131
+ end
132
+ end
133
+
134
+ @logger.info("Listening to path: #{path}")
135
+ @logger.info("Ignoring #{ignores.length} paths:")
136
+ ignores.each do |ignore|
137
+ @logger.info("-- #{ignore}")
138
+ end
139
+
140
+ listopts = { ignore: ignores, force_polling: options[:poll] }
141
+ Driskell::Listen.to(path, listopts, &callback)
142
+ end
143
+
144
+ # Create the callback that lets us know when we've been interrupted
145
+ queue = Queue.new
146
+ callback = lambda do
147
+ # This needs to execute in another thread because Thread
148
+ # synchronization can't happen in a trap context.
149
+ Thread.new { queue << true }
150
+ end
151
+
152
+ # Run the listeners in a busy block so that we can cleanly
153
+ # exit once we receive an interrupt.
154
+ Vagrant::Util::Busy.busy(callback) do
155
+ listeners.each(&:start)
156
+ queue.pop
157
+ listeners.each do |listener|
158
+ listener.stop if listener.state != :stopped
159
+ end
160
+ end
161
+
162
+ 0
163
+ end
164
+
165
+ # This is the callback that is called when any changes happen
166
+ def callback(path, opts, options, modified, added, removed)
167
+ @logger.info("File change callback called for #{path}!")
168
+ @logger.info(" - Modified: #{modified.inspect}")
169
+ @logger.info(" - Added: #{added.inspect}")
170
+ @logger.info(" - Removed: #{removed.inspect}")
171
+
172
+ # Perform the sync for each machine
173
+ opts.each do |path_opts|
174
+ # Reload so we get the latest ID
175
+ path_opts[:machine].reload
176
+ if !path_opts[:machine].id || path_opts[:machine].id == ''
177
+ # Skip since we can't get SSH info without an ID
178
+ next
179
+ end
180
+
181
+ begin
182
+ # If we have any removals or have disabled incremental, perform a
183
+ # single full sync
184
+ # It's seemingly impossible to ask rsync to only do a deletion
185
+ if !options[:incremental] || !removed.empty?
186
+ removed.each do |remove|
187
+ path_opts[:machine].ui.info(
188
+ I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_remove',
189
+ path: remove))
190
+ end
191
+
192
+ [modified, added].each do |changes|
193
+ changes.each do |change|
194
+ path_opts[:machine].ui.info(
195
+ I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_change',
196
+ path: change))
197
+ end
198
+ end
199
+
200
+ SyncHelper.sync_single(
201
+ path_opts[:machine],
202
+ path_opts[:machine].ssh_info,
203
+ path_opts[:opts]
204
+ )
205
+ elsif !modified.empty? || !added.empty?
206
+ synchronize_changes(path, path_opts, options, [modified, added])
207
+ end
208
+
209
+ path_opts[:machine].ui.info(
210
+ I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_synced'))
211
+ rescue Vagrant::Errors::MachineGuestNotReady
212
+ # Error communicating to the machine, probably a reload or
213
+ # halt is happening. Just notify the user but don't fail out.
214
+ path_opts[:machine].ui.error(
215
+ I18n.t('vagrant.plugins.vagrant-reflect.'\
216
+ 'rsync_communicator_not_ready_callback'))
217
+ rescue Vagrant::Errors::VagrantError => e
218
+ path_opts[:machine].ui.error(e)
219
+ end
220
+ end
221
+ end
222
+
223
+ # Helper to pull the next change from a set of changes of the form
224
+ # [ set1, set2, set3 ]
225
+ # Sets are removed as they are emptied
226
+ def next_change(sets, path)
227
+ line = sets[0].pop
228
+ sets.shift while !sets.empty? && sets[0].empty?
229
+ line[path.length..line.length] + "\n"
230
+ end
231
+
232
+ def synchronize_changes(path, path_opts, options, sets)
233
+ # Grab the first change
234
+ sets.shift while sets[0].empty?
235
+ line = false
236
+
237
+ # Pass the list of changes to rsync so we quickly synchronise only
238
+ # the changed files instead of the whole folder
239
+ SyncHelper.sync_single(
240
+ path_opts[:machine],
241
+ path_opts[:machine].ssh_info,
242
+ path_opts[:opts].merge(from_stdin: true)
243
+ ) do |what, io|
244
+ next if what != :stdin
245
+
246
+ if line.nil?
247
+ io.close_write
248
+ next
249
+ end
250
+
251
+ begin
252
+ loop do
253
+ # If we don't have a line, grab one and print it
254
+ unless line
255
+ line = next_change(sets, path)
256
+ path_opts[:machine].ui.info(
257
+ I18n.t('vagrant.plugins.vagrant-reflect.rsync_auto_increment',
258
+ path: line))
259
+ end
260
+
261
+ # Handle partial writes
262
+ n = io.write_nonblock(line)
263
+ if n < line.length
264
+ line = line[n..line.length]
265
+ break
266
+ end
267
+
268
+ # When we've finished giving rsync the file list, set line to nil
269
+ # and return - on the next notify we will EOT stdin
270
+ if sets.empty?
271
+ line = nil
272
+ break
273
+ end
274
+
275
+ # Request a new line for next write
276
+ line = false
277
+ end
278
+ rescue IO::WaitWritable, Errno::EINTR
279
+ # Ignore
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,167 @@
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 SyncHelper
9
+ # This converts an rsync exclude pattern to a regular expression
10
+ # we can send to Listen.
11
+ def self.exclude_to_regexp(path, exclude)
12
+ start_anchor = false
13
+
14
+ if exclude.start_with?('/')
15
+ start_anchor = true
16
+ exclude = exclude[1..-1]
17
+ end
18
+
19
+ path = "#{path}/" unless path.end_with?('/')
20
+ regexp = "^#{Regexp.escape(path)}"
21
+ regexp += '.*' unless start_anchor
22
+
23
+ # This is REALLY ghetto, but its a start. We can improve and
24
+ # keep unit tests passing in the future.
25
+ exclude = exclude.gsub('**', '|||GLOBAL|||')
26
+ exclude = exclude.gsub('*', '|||PATH|||')
27
+ exclude = exclude.gsub('|||PATH|||', '[^/]*')
28
+ exclude = exclude.gsub('|||GLOBAL|||', '.*')
29
+ regexp += exclude
30
+
31
+ Regexp.new(regexp)
32
+ end
33
+
34
+ def self.sync_single(machine, ssh_info, opts, &block)
35
+ # Folder info
36
+ guestpath = opts[:guestpath]
37
+ hostpath = opts[:hostpath]
38
+ hostpath = File.expand_path(hostpath, machine.env.root_path)
39
+ hostpath = Vagrant::Util::Platform.fs_real_path(hostpath).to_s
40
+
41
+ if Vagrant::Util::Platform.windows?
42
+ # rsync for Windows expects cygwin style paths, always.
43
+ hostpath = Vagrant::Util::Platform.cygwin_path(hostpath)
44
+ end
45
+
46
+ # Make sure the host path ends with a "/" to avoid creating
47
+ # a nested directory...
48
+ hostpath += '/' unless hostpath.end_with?('/')
49
+
50
+ # Folder options
51
+ opts[:owner] ||= ssh_info[:username]
52
+ opts[:group] ||= ssh_info[:username]
53
+
54
+ # Connection information
55
+ username = ssh_info[:username]
56
+ host = ssh_info[:host]
57
+ proxy_command = ''
58
+ if ssh_info[:proxy_command]
59
+ proxy_command = "-o ProxyCommand='#{ssh_info[:proxy_command]}' "
60
+ end
61
+
62
+ rsh = [
63
+ "ssh -p #{ssh_info[:port]} " +
64
+ proxy_command +
65
+ '-o StrictHostKeyChecking=no '\
66
+ '-o IdentitiesOnly=true '\
67
+ '-o UserKnownHostsFile=/dev/null',
68
+ ssh_info[:private_key_path].map { |p| "-i '#{p}'" }
69
+ ].flatten.join(' ')
70
+
71
+ # Exclude some files by default, and any that might be configured
72
+ # by the user.
73
+ excludes = ['.vagrant/']
74
+ excludes += Array(opts[:exclude]).map(&:to_s) if opts[:exclude]
75
+ excludes.uniq!
76
+
77
+ # Get the command-line arguments
78
+ args = nil
79
+ args = Array(opts[:args]).dup if opts[:args]
80
+ args ||= ['--verbose', '--archive', '--delete', '-z', '--copy-links']
81
+
82
+ # On Windows, we have to set a default chmod flag to avoid permission
83
+ # issues
84
+ if Vagrant::Util::Platform.windows?
85
+ unless args.any? { |arg| arg.start_with?('--chmod=') }
86
+ # Ensures that all non-masked bits get enabled
87
+ args << '--chmod=ugo=rwX'
88
+
89
+ # Remove the -p option if --archive is enabled (--archive equals
90
+ # -rlptgoD) otherwise new files will not have the destination-default
91
+ # permissions
92
+ args << '--no-perms' if
93
+ args.include?('--archive') || args.include?('-a')
94
+ end
95
+ end
96
+
97
+ # Disable rsync's owner/group preservation (implied by --archive) unless
98
+ # specifically requested, since we adjust owner/group to match shared
99
+ # folder setting ourselves.
100
+ args << '--no-owner' unless
101
+ args.include?('--owner') || args.include?('-o')
102
+ args << '--no-group' unless
103
+ args.include?('--group') || args.include?('-g')
104
+
105
+ # Tell local rsync how to invoke remote rsync with sudo
106
+ if machine.guest.capability?(:rsync_command)
107
+ args << '--rsync-path' << machine.guest.capability(:rsync_command)
108
+ end
109
+
110
+ args << '--files-from=-' if opts[:from_stdin] && block_given?
111
+
112
+ # Build up the actual command to execute
113
+ command = [
114
+ 'rsync',
115
+ args,
116
+ '-e', rsh,
117
+ excludes.map { |e| ['--exclude', e] },
118
+ hostpath,
119
+ "#{username}@#{host}:#{guestpath}"
120
+ ].flatten
121
+
122
+ # The working directory should be the root path
123
+ command_opts = {}
124
+ command_opts[:workdir] = machine.env.root_path.to_s
125
+ command_opts[:notify] = [:stdin] if opts[:from_stdin] && block_given?
126
+
127
+ if opts[:from_stdin] && block_given?
128
+ machine.ui.info(
129
+ I18n.t(
130
+ 'vagrant.plugins.vagrant-reflect.rsync_folder_changes',
131
+ guestpath: guestpath,
132
+ hostpath: hostpath))
133
+ else
134
+ machine.ui.info(
135
+ I18n.t(
136
+ 'vagrant.plugins.vagrant-reflect.rsync_folder',
137
+ guestpath: guestpath,
138
+ hostpath: hostpath))
139
+ end
140
+ if excludes.length > 1
141
+ machine.ui.info(
142
+ I18n.t(
143
+ 'vagrant.plugins.vagrant-reflect.rsync_folder_excludes',
144
+ excludes: excludes.inspect))
145
+ end
146
+
147
+ # If we have tasks to do before rsyncing, do those.
148
+ if machine.guest.capability?(:rsync_pre)
149
+ machine.guest.capability(:rsync_pre, opts)
150
+ end
151
+
152
+ r = Vagrant::Util::SubprocessPatched.execute(*(command + [command_opts]), &block)
153
+ if r.exit_code != 0
154
+ raise Vagrant::Errors::RSyncError,
155
+ command: command.join(' '),
156
+ guestpath: guestpath,
157
+ hostpath: hostpath,
158
+ stderr: r.stderr
159
+ end
160
+
161
+ # If we have tasks to do after rsyncing, do those.
162
+ if machine.guest.capability?(:rsync_post)
163
+ machine.guest.capability(:rsync_post, opts)
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,316 @@
1
+ require 'thread'
2
+
3
+ require 'childprocess'
4
+ require 'log4r'
5
+
6
+ require 'vagrant/util/io'
7
+ require 'vagrant/util/platform'
8
+ require 'vagrant/util/safe_chdir'
9
+ require 'vagrant/util/which'
10
+
11
+ module Vagrant
12
+ module Util
13
+ # Execute a command in a subprocess, gathering the results and
14
+ # exit status.
15
+ #
16
+ # This class also allows you to read the data as it is outputted
17
+ # from the subprocess in real time, by simply passing a block to
18
+ # the execute method.
19
+ class SubprocessPatched
20
+ # Convenience method for executing a method.
21
+ def self.execute(*command, &block)
22
+ new(*command).execute(&block)
23
+ end
24
+
25
+ def initialize(*command)
26
+ @options = command.last.is_a?(Hash) ? command.pop : {}
27
+ @command = command.dup
28
+ @command = @command.map { |s| s.encode(Encoding.default_external) }
29
+ @command[0] = Which.which(@command[0]) if !File.file?(@command[0])
30
+ if !@command[0]
31
+ raise Errors::CommandUnavailableWindows, file: command[0] if Platform.windows?
32
+ raise Errors::CommandUnavailable, file: command[0]
33
+ end
34
+
35
+ @logger = Log4r::Logger.new("vagrant::util::subprocess")
36
+ end
37
+
38
+ def execute
39
+ # Get the timeout, if we have one
40
+ timeout = @options[:timeout]
41
+
42
+ # Get the working directory
43
+ workdir = @options[:workdir] || Dir.pwd
44
+
45
+ # Get what we're interested in being notified about
46
+ notify = @options[:notify] || []
47
+ notify = [notify] if !notify.is_a?(Array)
48
+ if notify.empty? && block_given?
49
+ # If a block is given, subscribers must be given, otherwise the
50
+ # block is never called. This is usually NOT what you want, so this
51
+ # is an error.
52
+ message = "A list of notify subscriptions must be given if a block is given"
53
+ raise ArgumentError, message
54
+ end
55
+
56
+ # Let's get some more useful booleans that we access a lot so
57
+ # we're not constantly calling an `include` check
58
+ notify_table = {}
59
+ notify_table[:stderr] = notify.include?(:stderr)
60
+ notify_table[:stdout] = notify.include?(:stdout)
61
+ notify_stdin = notify.include?(:stdin)
62
+
63
+ # Build the ChildProcess
64
+ @logger.info("Starting process: #{@command.inspect}")
65
+ process = ChildProcess.build(*@command)
66
+
67
+ # Create the pipes so we can read the output in real time as
68
+ # we execute the command.
69
+ stdout, stdout_writer = ::IO.pipe
70
+ stderr, stderr_writer = ::IO.pipe
71
+ process.io.stdout = stdout_writer
72
+ process.io.stderr = stderr_writer
73
+ process.duplex = true
74
+
75
+ # Special installer-related things
76
+ if Vagrant.in_installer?
77
+ installer_dir = ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"].to_s.downcase
78
+
79
+ # If we're in an installer on Mac and we're executing a command
80
+ # in the installer context, then force DYLD_LIBRARY_PATH to look
81
+ # at our libs first.
82
+ if Platform.darwin?
83
+ if @command[0].downcase.include?(installer_dir)
84
+ @logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...")
85
+ process.environment["DYLD_LIBRARY_PATH"] =
86
+ "#{installer_dir}/lib:#{ENV["DYLD_LIBRARY_PATH"]}"
87
+ else
88
+ @logger.debug("Command not in installer, not touching env vars.")
89
+ end
90
+
91
+ if File.setuid?(@command[0]) || File.setgid?(@command[0])
92
+ @logger.info("Command is setuid/setgid, clearing DYLD_LIBRARY_PATH")
93
+ process.environment["DYLD_LIBRARY_PATH"] = ""
94
+ end
95
+ end
96
+
97
+ # If the command that is being run is not inside the installer, reset
98
+ # the original environment - this is required for shelling out to
99
+ # other subprocesses that depend on environment variables (like Ruby
100
+ # and $GEM_PATH for example)
101
+ internal = [installer_dir, Vagrant.user_data_path.to_s.downcase].
102
+ any? { |path| @command[0].downcase.include?(path) }
103
+ if !internal
104
+ @logger.info("Command not in installer, restoring original environment...")
105
+ jailbreak(process.environment)
106
+ end
107
+ else
108
+ @logger.info("Vagrant not running in installer, restoring original environment...")
109
+ jailbreak(process.environment)
110
+ end
111
+
112
+ # Set the environment on the process if we must
113
+ if @options[:env]
114
+ @options[:env].each do |k, v|
115
+ process.environment[k] = v
116
+ end
117
+ end
118
+
119
+ # Start the process
120
+ begin
121
+ SafeChdir.safe_chdir(workdir) do
122
+ process.start
123
+ end
124
+ rescue ChildProcess::LaunchError => ex
125
+ # Raise our own version of the error so that users of the class
126
+ # don't need to be aware of ChildProcess
127
+ raise LaunchError.new(ex.message)
128
+ end
129
+
130
+ # Make sure the stdin does not buffer
131
+ process.io.stdin.sync = true
132
+
133
+ if RUBY_PLATFORM != "java"
134
+ # On Java, we have to close after. See down the method...
135
+ # Otherwise, we close the writers right here, since we're
136
+ # not on the writing side.
137
+ stdout_writer.close
138
+ stderr_writer.close
139
+ end
140
+
141
+ # Create a dictionary to store all the output we see.
142
+ io_data = { stdout: "", stderr: "" }
143
+
144
+ # Record the start time for timeout purposes
145
+ start_time = Time.now.to_i
146
+
147
+ @logger.debug("Selecting on IO")
148
+ while true
149
+ # PATCH BEGINS HERE TO FIX STDIN CLOSURE
150
+ writers = notify_stdin && !process.io.stdin.closed? ? [process.io.stdin] : []
151
+ # PATCH END
152
+ results = ::IO.select([stdout, stderr], writers, nil, 0.1)
153
+ results ||= []
154
+ readers = results[0]
155
+ writers = results[1]
156
+
157
+ # Check if we have exceeded our timeout
158
+ raise TimeoutExceeded, process.pid if timeout && (Time.now.to_i - start_time) > timeout
159
+
160
+ # Check the readers to see if they're ready
161
+ if readers && !readers.empty?
162
+ readers.each do |r|
163
+ # Read from the IO object
164
+ data = IO.read_until_block(r)
165
+
166
+ # We don't need to do anything if the data is empty
167
+ next if data.empty?
168
+
169
+ io_name = r == stdout ? :stdout : :stderr
170
+ @logger.debug("#{io_name}: #{data.chomp}")
171
+
172
+ io_data[io_name] += data
173
+ yield io_name, data if block_given? && notify_table[io_name]
174
+ end
175
+ end
176
+
177
+ # Break out if the process exited. We have to do this before
178
+ # attempting to write to stdin otherwise we'll get a broken pipe
179
+ # error.
180
+ break if process.exited?
181
+
182
+ # Check the writers to see if they're ready, and notify any listeners
183
+ if writers && !writers.empty? && block_given?
184
+ yield :stdin, process.io.stdin
185
+ end
186
+ end
187
+
188
+ # Wait for the process to end.
189
+ begin
190
+ remaining = (timeout || 32000) - (Time.now.to_i - start_time)
191
+ remaining = 0 if remaining < 0
192
+ @logger.debug("Waiting for process to exit. Remaining to timeout: #{remaining}")
193
+
194
+ process.poll_for_exit(remaining)
195
+ rescue ChildProcess::TimeoutError
196
+ raise TimeoutExceeded, process.pid
197
+ end
198
+
199
+ @logger.debug("Exit status: #{process.exit_code}")
200
+
201
+ # Read the final output data, since it is possible we missed a small
202
+ # amount of text between the time we last read data and when the
203
+ # process exited.
204
+ [stdout, stderr].each do |io|
205
+ # Read the extra data, ignoring if there isn't any
206
+ extra_data = IO.read_until_block(io)
207
+ next if extra_data == ""
208
+
209
+ # Log it out and accumulate
210
+ io_name = io == stdout ? :stdout : :stderr
211
+ io_data[io_name] += extra_data
212
+ @logger.debug("#{io_name}: #{extra_data.chomp}")
213
+
214
+ # Yield to any listeners any remaining data
215
+ yield io_name, extra_data if block_given? && notify_table[io_name]
216
+ end
217
+
218
+ if RUBY_PLATFORM == "java"
219
+ # On JRuby, we need to close the writers after the process,
220
+ # for some reason. See GH-711.
221
+ stdout_writer.close
222
+ stderr_writer.close
223
+ end
224
+
225
+ # Return an exit status container
226
+ return Result.new(process.exit_code, io_data[:stdout], io_data[:stderr])
227
+ ensure
228
+ if process && process.alive?
229
+ # Make sure no matter what happens, the process exits
230
+ process.stop(2)
231
+ end
232
+ end
233
+
234
+ protected
235
+
236
+ # An error which raises when a process fails to start
237
+ class LaunchError < StandardError; end
238
+
239
+ # An error which occurs when the process doesn't end within
240
+ # the given timeout.
241
+ class TimeoutExceeded < StandardError
242
+ attr_reader :pid
243
+
244
+ def initialize(pid)
245
+ super()
246
+ @pid = pid
247
+ end
248
+ end
249
+
250
+ # Container class to store the results of executing a subprocess.
251
+ class Result
252
+ attr_reader :exit_code
253
+ attr_reader :stdout
254
+ attr_reader :stderr
255
+
256
+ def initialize(exit_code, stdout, stderr)
257
+ @exit_code = exit_code
258
+ @stdout = stdout
259
+ @stderr = stderr
260
+ end
261
+ end
262
+
263
+ private
264
+
265
+ # This is, quite possibly, the saddest function in all of Vagrant.
266
+ #
267
+ # If a user is running Vagrant via Bundler (but not via the official
268
+ # installer), we want to reset to the "original" environment so that when
269
+ # shelling out to other Ruby processes (specifically), the original
270
+ # environment is restored. This is super important for things like
271
+ # rbenv and chruby, who rely on environment variables to locate gems, but
272
+ # Bundler stomps on those environment variables like an angry T-Rex after
273
+ # watching Jurassic Park 2 and realizing they replaced you with CGI.
274
+ #
275
+ # If a user is running in Vagrant via the official installer, BUT trying
276
+ # to execute a subprocess *outside* of the installer, we want to reset to
277
+ # the "original" environment. In this case, the Vagrant installer actually
278
+ # knows what the original environment was and replaces it completely.
279
+ #
280
+ # Finally, we reset any Bundler-specific environment variables, since the
281
+ # subprocess being called could, itself, be Bundler. And Bundler does not
282
+ # behave very nicely in these circumstances.
283
+ #
284
+ # This function was added in Vagrant 1.7.3, but there is a failsafe
285
+ # because the author doesn't trust himself that this functionality won't
286
+ # break existing assumptions, so users can specify
287
+ # `VAGRANT_SKIP_SUBPROCESS_JAILBREAK` and none of the above will happen.
288
+ #
289
+ # This function modifies the given hash in place!
290
+ #
291
+ # @return [nil]
292
+ def jailbreak(env = {})
293
+ return if ENV.key?("VAGRANT_SKIP_SUBPROCESS_JAILBREAK")
294
+
295
+ env.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV)
296
+ env.merge!(Vagrant.original_env)
297
+
298
+ # Bundler does this, so I guess we should as well, since I think it
299
+ # other subprocesses that use Bundler will reload it
300
+ env["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"]
301
+
302
+ # Replace all current environment BUNDLE_ variables to nil
303
+ ENV.each do |k,_|
304
+ env[k] = nil if k[0,7] == "BUNDLE_"
305
+ end
306
+
307
+ # If RUBYOPT was set, unset it with Bundler
308
+ if ENV.key?("RUBYOPT")
309
+ env["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "")
310
+ end
311
+
312
+ nil
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,12 @@
1
+ # Vagrant Reflect plugin
2
+ module VagrantReflect
3
+ # A Vagrant Plugin
4
+ class Plugin < Vagrant.plugin('2')
5
+ name 'Vagrant Reflect'
6
+
7
+ command 'reflect' do
8
+ require_relative 'command/reflect'
9
+ Command::Reflect
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module VagrantReflect
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'vagrant'
2
+
3
+ I18n.load_path << File.join(
4
+ File.dirname(File.dirname(__FILE__)), 'templates/locales/en.yml')
5
+
6
+ require 'vagrant-reflect/plugin'
7
+ require 'vagrant-reflect/version'
@@ -0,0 +1,37 @@
1
+ en:
2
+ vagrant:
3
+ plugins:
4
+ vagrant-reflect:
5
+ rsync_auto_initial: |-
6
+ Doing an initial rsync...
7
+ rsync_auto_new_folders: |-
8
+ New synced folders were added to the Vagrantfile since running
9
+ `vagrant reload`. If these new synced folders are backed by rsync,
10
+ they won't be automatically synced until a `vagrant reload` is run.
11
+ rsync_auto_no_paths: |-
12
+ There are no paths to watch! This is either because you have no
13
+ synced folders using rsync, or any rsync synced folders you have
14
+ have specified `rsync_auto` to be false.
15
+ rsync_auto_path: |-
16
+ Watching: %{path}
17
+ rsync_auto_increment: |-
18
+ Sending change: %{path}
19
+ rsync_auto_change: |-
20
+ Processing change: %{path}
21
+ rsync_auto_remove: |-
22
+ Processing removal: %{path}
23
+ rsync_auto_synced: |-
24
+ Synchronization completed
25
+ rsync_communicator_not_ready_callback: |-
26
+ Failed to connect to remote machine. This is usually caused by the
27
+ machine rebooting or being halted. Please make sure the machine is
28
+ running, and modify a file to try again.
29
+ rsync_folder: |-
30
+ Rsyncing folder: %{hostpath} => %{guestpath}
31
+ rsync_folder_changes: |-
32
+ Rsyncing changes only: %{hostpath} => %{guestpath}
33
+ rsync_folder_excludes: " - Exclude: %{excludes}"
34
+ rsync_proxy_machine: |-
35
+ The provider ('%{provider}') for the machine '%{name}' is
36
+ using a proxy machine. RSync will sync to this proxy
37
+ instead of directly to the environment itself.
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-reflect
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jason Woods
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: driskell-listen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.6.10
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.6.10
27
+ description: Vagrant Reflect
28
+ email:
29
+ - devel@jasonwoods.me.uk
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/vagrant-reflect.rb
35
+ - lib/vagrant-reflect/command/reflect.rb
36
+ - lib/vagrant-reflect/helper.rb
37
+ - lib/vagrant-reflect/patched/subprocess.rb
38
+ - lib/vagrant-reflect/plugin.rb
39
+ - lib/vagrant-reflect/version.rb
40
+ - templates/locales/en.yml
41
+ homepage: https://github.com/driskell/vagrant-reflect
42
+ licenses:
43
+ - Apache
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project: nowarning
61
+ rubygems_version: 2.4.2
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: A better vagrant rsync-auto
65
+ test_files: []
66
+ has_rdoc: