vagrant-reflect 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: