vagrant-mirror 0.1.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +3 -0
- data/CONTRIBUTING.md +43 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +85 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +153 -0
- data/Rakefile +29 -0
- data/lib/vagrant-mirror/command.rb +71 -0
- data/lib/vagrant-mirror/config.rb +73 -0
- data/lib/vagrant-mirror/errors.rb +42 -0
- data/lib/vagrant-mirror/listener/host.rb +42 -0
- data/lib/vagrant-mirror/middleware/base.rb +65 -0
- data/lib/vagrant-mirror/middleware/mirror.rb +108 -0
- data/lib/vagrant-mirror/middleware/sync.rb +92 -0
- data/lib/vagrant-mirror/rsync.rb +91 -0
- data/lib/vagrant-mirror/sync/all.rb +60 -0
- data/lib/vagrant-mirror/sync/base.rb +98 -0
- data/lib/vagrant-mirror/sync/changes.rb +61 -0
- data/lib/vagrant-mirror/version.rb +5 -0
- data/lib/vagrant-mirror.rb +34 -0
- data/lib/vagrant_init.rb +4 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/vagrant-mirror/command_spec.rb +91 -0
- data/spec/vagrant-mirror/config_spec.rb +161 -0
- data/spec/vagrant-mirror/listener/host_spec.rb +96 -0
- data/spec/vagrant-mirror/middleware/mirror_spec.rb +188 -0
- data/spec/vagrant-mirror/middleware/sync_spec.rb +152 -0
- data/spec/vagrant-mirror/rsync_spec.rb +75 -0
- data/spec/vagrant-mirror_spec.rb +5 -0
- data/vagrant-mirror.gemspec +27 -0
- metadata +153 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
# Monitors changes on the host and guest instance, and propogates any new, changed
|
2
|
+
# or deleted files between machines. Note that this will block the vagrant
|
3
|
+
# execution on the host.
|
4
|
+
#
|
5
|
+
# @author Andrew Coulton < andrew@ingerator.com >
|
6
|
+
module Vagrant
|
7
|
+
module Mirror
|
8
|
+
module Middleware
|
9
|
+
class Mirror < Base
|
10
|
+
|
11
|
+
# Loads the rest of the middlewares first, then finishes up by running
|
12
|
+
# the mirror middleware. This allows the listener to start after the
|
13
|
+
# instance has been provisioned.
|
14
|
+
#
|
15
|
+
# @param [Vagrant::Action::Environment] The environment
|
16
|
+
def call(env)
|
17
|
+
@app.call(env)
|
18
|
+
|
19
|
+
mirrors = env[:vm].config.mirror.folders
|
20
|
+
if !mirrors.empty?
|
21
|
+
execute(mirrors, env)
|
22
|
+
else
|
23
|
+
env[:ui].info("No vagrant-mirror mirrored folders configured for this box")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
# Mirrors the folder pairs configured in the vagrantfile
|
30
|
+
#
|
31
|
+
# @param [Array] The folder pairs to synchronise
|
32
|
+
# @param [Vagrant::Action::Environment] The environment
|
33
|
+
def execute(mirrors, env)
|
34
|
+
ui = env[:ui]
|
35
|
+
ui.info("Beginning directory mirroring")
|
36
|
+
|
37
|
+
begin
|
38
|
+
workers = []
|
39
|
+
|
40
|
+
# Create a thread to work off the queue for each folder
|
41
|
+
each_mirror(mirrors) do | host_path, guest_sf_path, mirror_config |
|
42
|
+
workers << Thread.new do
|
43
|
+
# Set up the listener and the changes queue
|
44
|
+
Thread.current["queue"] = Queue.new
|
45
|
+
host_listener = Vagrant::Mirror::Listener::Host.new(host_path, Thread.current["queue"])
|
46
|
+
rsync = Vagrant::Mirror::Rsync.new(env[:vm], guest_sf_path, host_path, mirror_config)
|
47
|
+
|
48
|
+
# Start listening and store the thread reference
|
49
|
+
Thread.current["listener"] = host_listener.listen
|
50
|
+
|
51
|
+
# Just poll indefinitely waiting for changes or to be told to quit
|
52
|
+
quit = false
|
53
|
+
while !quit
|
54
|
+
change = Thread.current["queue"].pop
|
55
|
+
if (change[:quit])
|
56
|
+
quit = true
|
57
|
+
else
|
58
|
+
# Handle removed files first - guard sometimes flagged as deleted when they aren't
|
59
|
+
# So we first check if the file has been deleted on the host. If so, we delete on
|
60
|
+
# the guest, otherwise we add to the list to rsync in case there are changes
|
61
|
+
changes = []
|
62
|
+
change[:removed].each do | relpath |
|
63
|
+
if (File.exists?(File.join(host_path, relpath)))
|
64
|
+
changes << relpath
|
65
|
+
else
|
66
|
+
target = "#{mirror_config[:guest_path]}/#{relpath}"
|
67
|
+
ui.warn("XX Deleting #{target}")
|
68
|
+
env[:vm].channel.sudo("rm #{target}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add to the list of deletions with each of the modified and added files
|
73
|
+
changes = changes + change[:added] + change[:modified]
|
74
|
+
changes.each do | relpath |
|
75
|
+
ui.info(">> #{relpath}")
|
76
|
+
rsync.run(relpath)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Beep if configured
|
80
|
+
if (mirror_config[:beep])
|
81
|
+
print "\a"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Wait for the listener thread to exit
|
89
|
+
workers.each do | thread |
|
90
|
+
thread.join
|
91
|
+
end
|
92
|
+
rescue RuntimeError => e
|
93
|
+
# Pass through Vagrant errors
|
94
|
+
if e.is_a? Vagrant::Errors::VagrantError
|
95
|
+
raise
|
96
|
+
end
|
97
|
+
|
98
|
+
# Convert to a vagrant error descendant so that the box is not cleaned up
|
99
|
+
raise Vagrant::Mirror::Errors::Error.new("Vagrant-mirror caught a #{e.class.name} - #{e.message}")
|
100
|
+
end
|
101
|
+
|
102
|
+
ui.success("Completed directory synchronisation")
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
# Executes a full sync from the host to the guest instance based on the configuration
|
5
|
+
# in the vagrantfile, copying new or changed files to the guest as required.
|
6
|
+
#
|
7
|
+
# @author Andrew Coulton < andrew@ingerator.com >
|
8
|
+
module Vagrant
|
9
|
+
module Mirror
|
10
|
+
module Middleware
|
11
|
+
class Sync < Base
|
12
|
+
|
13
|
+
# Loads the rest of the middlewares first, then finishes up by running
|
14
|
+
# the sync middleware. This is required because the core share_folders
|
15
|
+
# middleware does not mount the shares until the very end of the process
|
16
|
+
# and we need to run after that
|
17
|
+
#
|
18
|
+
# @param [Vagrant::Action::Environment] The environment
|
19
|
+
def call(env)
|
20
|
+
@app.call(env)
|
21
|
+
|
22
|
+
mirrors = env[:vm].config.mirror.folders
|
23
|
+
if !mirrors.empty?
|
24
|
+
execute(mirrors, env)
|
25
|
+
else
|
26
|
+
env[:ui].info("No vagrant-mirror mirrored folders configured for this box")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# Synchronizes the folder pairs configured in the vagrantfile
|
33
|
+
#
|
34
|
+
# @param [Array] The folder pairs to synchronise
|
35
|
+
# @param [Vagrant::Action::Environment] The environment
|
36
|
+
def execute(mirrors, env)
|
37
|
+
ui = env[:ui]
|
38
|
+
ui.info("Beginning directory synchronisation at " + DateTime.now.iso8601)
|
39
|
+
|
40
|
+
begin
|
41
|
+
each_mirror(mirrors) do | host_path, guest_sf_path, mirror_config |
|
42
|
+
ui.info("Synchronising for #{host_path}")
|
43
|
+
|
44
|
+
# Create any required symlinks
|
45
|
+
mirror_config[:symlinks].each do | relpath |
|
46
|
+
relpath.sub!(/^\//, '')
|
47
|
+
source = "#{guest_sf_path}/#{relpath}"
|
48
|
+
target = "#{mirror_config[:guest_path]}/#{relpath}"
|
49
|
+
|
50
|
+
# Find the parent directory - we have to do this with regexp as we don't have the
|
51
|
+
# right filesystem to use File.expand
|
52
|
+
dirs = /^(.*)(\/.+)$/.match(target)
|
53
|
+
if (dirs)
|
54
|
+
target_dir = dirs[1]
|
55
|
+
else
|
56
|
+
target_dir = '/'
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create the host directory if required
|
60
|
+
host_dir = "#{host_path}/#{relpath}"
|
61
|
+
if ( ! File.exists?(host_dir))
|
62
|
+
FileUtils.mkdir_p(host_dir)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create the parent directory and create the symlink
|
66
|
+
ui.info("Creating link from #{target} to #{source}")
|
67
|
+
env[:vm].channel.sudo("rm -f #{target} && mkdir -p #{target_dir} && ln -s #{source} #{target}")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Trigger the sync on the remote host
|
71
|
+
ui.info("Synchronising from #{guest_sf_path} to #{mirror_config[:guest_path]}")
|
72
|
+
rsync = Vagrant::Mirror::Rsync.new(env[:vm], guest_sf_path, host_path, mirror_config)
|
73
|
+
rsync.run('/')
|
74
|
+
end
|
75
|
+
|
76
|
+
rescue RuntimeError => e
|
77
|
+
# Pass through Vagrant errors
|
78
|
+
if e.is_a? Vagrant::Errors::VagrantError
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
|
82
|
+
# Convert to a vagrant error descendant so that the box is not cleaned up
|
83
|
+
raise Vagrant::Mirror::Errors::Error.new("Vagrant-mirror caught a #{e.class.name} - #{e.message}")
|
84
|
+
end
|
85
|
+
|
86
|
+
ui.success("Completed directory synchronisation at " + DateTime.now.iso8601)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Executes rsync on the guest to update files between the shared folder and the local virtual disk.
|
2
|
+
# Propogates deletes if configured to do so, but not otherwise.
|
3
|
+
#
|
4
|
+
# @author Andrew Coulton < andrew@ingerator.com >
|
5
|
+
module Vagrant
|
6
|
+
module Mirror
|
7
|
+
class Rsync
|
8
|
+
# @return [Vagrant::VM] The VM to mirror on
|
9
|
+
attr_reader :vm
|
10
|
+
|
11
|
+
# @return [String] The path to the virtualbox shared folder on the guest
|
12
|
+
attr_reader :guest_sf_path
|
13
|
+
|
14
|
+
# @return [String] The path to the virtualbox shared folder on the host
|
15
|
+
attr_reader :host_path
|
16
|
+
|
17
|
+
# @return [Bool] Whether Rsync should delete unexpected files
|
18
|
+
attr_reader :delete
|
19
|
+
|
20
|
+
# @return [Array] The array of paths to exclude
|
21
|
+
attr_reader :excludes
|
22
|
+
|
23
|
+
# @return [String] The exclude paths formatted as an array of rsync exclude arguments
|
24
|
+
attr_reader :exclude_args
|
25
|
+
|
26
|
+
# @return [String] The path to the mirror folder on the guest
|
27
|
+
attr_reader :guest_path
|
28
|
+
|
29
|
+
# Creates an instance
|
30
|
+
#
|
31
|
+
# @param [Vagrant::VM] The VM to mirror on
|
32
|
+
# @param [String] The path of the shared folder on the guest to use as the rsync source
|
33
|
+
# @param [String] The path of the shared folder on the host - used to check whether a path is a directory
|
34
|
+
# @param [Hash] The config.mirror options hash with the guest mirror destination, delete, etc
|
35
|
+
def initialize(vm, guest_sf_path, host_path, mirror_config)
|
36
|
+
@vm = vm
|
37
|
+
@guest_sf_path = guest_sf_path
|
38
|
+
@host_path = host_path
|
39
|
+
@delete = mirror_config[:delete]
|
40
|
+
@excludes = mirror_config[:exclude]
|
41
|
+
@guest_path = mirror_config[:guest_path]
|
42
|
+
|
43
|
+
# Build the exclude argument array
|
44
|
+
@exclude_args = []
|
45
|
+
if (@excludes.count > 0)
|
46
|
+
@excludes.each do | exclude |
|
47
|
+
exclude_args << "--exclude '#{exclude}'"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Run rsync on the guest to update a path - either the whole mirror directory or individual
|
53
|
+
# files and folders within it.
|
54
|
+
#
|
55
|
+
# @param [String] The path to run in
|
56
|
+
def run(path)
|
57
|
+
# Strip a leading / off the path to avoid any problems
|
58
|
+
path.sub!(/^\//, '')
|
59
|
+
|
60
|
+
# Build the source and destination paths
|
61
|
+
source = "#{guest_sf_path}/#{path}"
|
62
|
+
dest = "#{guest_path}/#{path}"
|
63
|
+
|
64
|
+
# Check if the source is a directory on the host - if so, add a / for rsync
|
65
|
+
if ((path != '') && (File.directory?(File.join(host_path, path))))
|
66
|
+
source << '/'
|
67
|
+
dest << '/'
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Build the rsync command
|
72
|
+
args = ['rsync -av']
|
73
|
+
|
74
|
+
if (delete)
|
75
|
+
args << '--del'
|
76
|
+
end
|
77
|
+
|
78
|
+
args = args + exclude_args
|
79
|
+
|
80
|
+
args << source
|
81
|
+
args << dest
|
82
|
+
|
83
|
+
cmd = args.join(' ')
|
84
|
+
|
85
|
+
# Run rsync
|
86
|
+
vm.channel.sudo(cmd)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Compares the contents of the host and guest paths and transfers any
|
2
|
+
# missing or modified paths to the other side of the mirror. If a whole
|
3
|
+
# directory is missing, uses recursive upload/download from the SFTP class,
|
4
|
+
# otherwise it iterates over and compares the directory contents.
|
5
|
+
#
|
6
|
+
# This class does not detect deletions - if a file is missing on one side
|
7
|
+
# of the mirror it will simply be replaced.
|
8
|
+
#
|
9
|
+
# @author Andrew Coulton < andrew@ingenerator.com >
|
10
|
+
module Vagrant
|
11
|
+
module Mirror
|
12
|
+
module Sync
|
13
|
+
class All < Base
|
14
|
+
|
15
|
+
# Compares a folder between guest and host, transferring any new or
|
16
|
+
# modified files in the right direction.
|
17
|
+
#
|
18
|
+
# @param [string] The base path to compare
|
19
|
+
def execute(path)
|
20
|
+
path = path.chomp('/')
|
21
|
+
host_dir = host_path(path).chomp('/')
|
22
|
+
guest_dir = guest_path(path).chomp('/')
|
23
|
+
|
24
|
+
if !@connection.exists?(guest_dir)
|
25
|
+
# Create the guest directory
|
26
|
+
@connection.mkdir(guest_dir)
|
27
|
+
end
|
28
|
+
|
29
|
+
if !File.exists?(host_dir)
|
30
|
+
# Create the host directory
|
31
|
+
FileUtils.mkdir_p(host_dir)
|
32
|
+
end
|
33
|
+
|
34
|
+
# If the guest path already exists, have to sync manually
|
35
|
+
# First get a combined listing of the two paths
|
36
|
+
all_files = @connection.dir_entries(guest_dir) | Dir.entries(host_dir)
|
37
|
+
all_files.each do | file |
|
38
|
+
# Ignore . and ..
|
39
|
+
if (file == '.') or (file == '..')
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get local paths
|
44
|
+
host_file = File.join(host_dir, file)
|
45
|
+
guest_file = File.join(guest_dir, file)
|
46
|
+
# Recurse for directories
|
47
|
+
if File.directory?(host_file) \
|
48
|
+
or ( !File.exists?(host_file) and @connection.directory?(guest_file))
|
49
|
+
execute("#{path}/#{file}")
|
50
|
+
else
|
51
|
+
# Transfer new/modified files between host and guest
|
52
|
+
compare_and_transfer(host_file, guest_file)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Base class for file sync actions, providing common required functionality
|
2
|
+
#
|
3
|
+
# @author Andrew Coulton < andrew@ingerator.com >
|
4
|
+
module Vagrant
|
5
|
+
module Mirror
|
6
|
+
module Sync
|
7
|
+
class Base
|
8
|
+
|
9
|
+
# Initialises the synchroniser
|
10
|
+
#
|
11
|
+
# @param [Vagrant::Mirror::Connection::SFTP] The sftp connection instance
|
12
|
+
# @param [String] The root path on the host to mirror
|
13
|
+
# @param [String] The root path on the guest to mirror
|
14
|
+
# @param [Vagrant::UI::Interface] The Vagrant UI class
|
15
|
+
def initialize(connection, host_path, guest_path, ui)
|
16
|
+
@connection = connection
|
17
|
+
@host_root = host_path
|
18
|
+
@guest_root = guest_path
|
19
|
+
@ui = ui
|
20
|
+
end
|
21
|
+
|
22
|
+
# ==================================================================
|
23
|
+
# Begin protected internal methods
|
24
|
+
# ==================================================================
|
25
|
+
protected
|
26
|
+
|
27
|
+
# Gets the absolute host path for a file
|
28
|
+
#
|
29
|
+
# @param [String] Relative path to the file
|
30
|
+
# @return [String] Absolute path to the file on the host
|
31
|
+
def host_path(relative_path)
|
32
|
+
File.join(@host_root, relative_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gets the absolute guest path for a file
|
36
|
+
#
|
37
|
+
# @param [String] Relative path to the file
|
38
|
+
# @return [String] Absolute path to the file on the guest
|
39
|
+
def guest_path(relative_path)
|
40
|
+
File.join(@guest_root, relative_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Gets the mtime of a file on the host from an absolute path
|
44
|
+
#
|
45
|
+
# @param [String] Absolute host path
|
46
|
+
# @return [Time] mtime, or nil if the file does not exist
|
47
|
+
def host_mtime(path)
|
48
|
+
if !File.exists?(path)
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
return File.mtime(path)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Gets the mtime of a file on the guest from an absolute path
|
55
|
+
#
|
56
|
+
# @param [String] Absolute guest path
|
57
|
+
# @return [Time] mtime, or nil if the file does not exist
|
58
|
+
def guest_mtime(path)
|
59
|
+
@connection.mtime(path)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Compares files on the host and the guest and transfers the newest
|
63
|
+
# to the other side.
|
64
|
+
#
|
65
|
+
# @param [String] Absolute host path
|
66
|
+
# @param [String] Absolute guest path
|
67
|
+
def compare_and_transfer(host_file, guest_file)
|
68
|
+
|
69
|
+
# Get the mtimes
|
70
|
+
host_time = host_mtime(host_file)
|
71
|
+
guest_time = guest_mtime(guest_file)
|
72
|
+
|
73
|
+
# Check what to do
|
74
|
+
if (host_time.nil? and guest_time.nil?) then
|
75
|
+
# Report an error
|
76
|
+
@ui.error("#{host_file} was not found on either the host or guest filesystem - cannot sync")
|
77
|
+
elsif (host_time == guest_time)
|
78
|
+
# Do nothing
|
79
|
+
return
|
80
|
+
elsif (guest_time.nil?)
|
81
|
+
# Transfer to guest
|
82
|
+
@connection.upload(host_file, guest_file, host_time)
|
83
|
+
elsif (host_time.nil?)
|
84
|
+
# Transfer to host
|
85
|
+
@connection.download(guest_file, host_file, guest_time)
|
86
|
+
elsif (host_time > guest_time)
|
87
|
+
# Transfer to guest
|
88
|
+
@connection.upload(host_file, guest_file, host_time)
|
89
|
+
elsif (host_time < guest_time)
|
90
|
+
# Transfer to guest
|
91
|
+
@connection.download(guest_file, host_file, guest_time)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Processes notified file additions, modifications and deletions notified by
|
2
|
+
# guard/listen, and replays them on the other side of the mirror.
|
3
|
+
# The execute method will be called for each changeset notified whether
|
4
|
+
# by guard or over the TCP socket (from guard on the guest). This could
|
5
|
+
# cause a race condition as changes on the guest trigger changes on the host
|
6
|
+
# and vice versa.
|
7
|
+
#
|
8
|
+
# Therefore, on notification of added or modified file, the class just
|
9
|
+
# syncs whichever is the newest file between the two machines. On deletion
|
10
|
+
# it will quietly fail if the file it is notified of has already been
|
11
|
+
# deleted.
|
12
|
+
#
|
13
|
+
# @author Andrew Coulton < andrew@ingenerator.com >
|
14
|
+
module Vagrant
|
15
|
+
module Mirror
|
16
|
+
module Sync
|
17
|
+
class Changes < Base
|
18
|
+
|
19
|
+
# Compares a single notified changeset and transfers any changed,
|
20
|
+
# modified or removed files in the right direction.
|
21
|
+
#
|
22
|
+
# @param [Symbol] Which side of the mirror the change was detected
|
23
|
+
# @param [Array] Array of added paths
|
24
|
+
# @param [Array] Array of changed paths
|
25
|
+
# @param [Array] Array of removed paths
|
26
|
+
def execute(source, added, modified, removed)
|
27
|
+
|
28
|
+
# Combine added and modified, they're the same for our purposes
|
29
|
+
changed = added + modified
|
30
|
+
changed.each do |file|
|
31
|
+
# Transfer the newest file to the other side, or do nothing
|
32
|
+
compare_and_transfer(host_path(file), guest_path(file))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Process deleted files
|
36
|
+
removed.each do |file|
|
37
|
+
# Expect a cascade - only delete on opposite side if exists
|
38
|
+
if source == :host
|
39
|
+
guest_file = guest_path(file)
|
40
|
+
if !guest_mtime(guest_file).nil?
|
41
|
+
@connection.delete(guest_file)
|
42
|
+
else
|
43
|
+
@ui.info("#{file} was not found on guest - nothing to delete")
|
44
|
+
end
|
45
|
+
elsif source == :guest
|
46
|
+
host_file = host_path(file)
|
47
|
+
if File.exists?(host_file)
|
48
|
+
File.delete(host_file)
|
49
|
+
else
|
50
|
+
@ui.info("#{file} was not found on host - nothing to delete")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Complete all transfers
|
56
|
+
@connection.finish_transfers
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'vagrant'
|
2
|
+
|
3
|
+
require 'vagrant-mirror/errors'
|
4
|
+
require 'vagrant-mirror/version'
|
5
|
+
require 'vagrant-mirror/config'
|
6
|
+
|
7
|
+
# Require the host listener
|
8
|
+
require 'vagrant-mirror/listener/host'
|
9
|
+
|
10
|
+
# Require the rsync wrapper
|
11
|
+
require 'vagrant-mirror/rsync'
|
12
|
+
|
13
|
+
# Require the middlewares
|
14
|
+
require 'vagrant-mirror/middleware/base'
|
15
|
+
require 'vagrant-mirror/middleware/sync'
|
16
|
+
require 'vagrant-mirror/middleware/mirror'
|
17
|
+
|
18
|
+
# Require the command
|
19
|
+
require 'vagrant-mirror/command'
|
20
|
+
|
21
|
+
# Register the config
|
22
|
+
Vagrant.config_keys.register(:mirror) { Vagrant::Mirror::Config }
|
23
|
+
|
24
|
+
# Register the command
|
25
|
+
Vagrant.commands.register(:mirror) { Vagrant::Mirror::Command }
|
26
|
+
|
27
|
+
# Add the sync middleware to the start stack
|
28
|
+
Vagrant.actions[:start].insert Vagrant::Action::VM::ShareFolders, Vagrant::Mirror::Middleware::Sync
|
29
|
+
|
30
|
+
# Add the mirror middleware to the standard stacks
|
31
|
+
Vagrant.actions[:start].insert Vagrant::Action::VM::Provision, Vagrant::Mirror::Middleware::Mirror
|
32
|
+
|
33
|
+
# Abort on unhandled exceptions in any thread
|
34
|
+
Thread.abort_on_exception = true
|
data/lib/vagrant_init.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'vagrant-mirror'
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
5
|
+
config.run_all_when_everything_filtered = true
|
6
|
+
config.filter_run :focus
|
7
|
+
|
8
|
+
# Run specs in random order to surface order dependencies. If you find an
|
9
|
+
# order dependency and want to debug it, you can fix the order by providing
|
10
|
+
# the seed, which is printed after each run.
|
11
|
+
# --seed 1234
|
12
|
+
config.order = 'random'
|
13
|
+
|
14
|
+
config.include Vagrant::TestHelpers
|
15
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
describe Vagrant::Mirror::Command do
|
2
|
+
let (:argv) { [] }
|
3
|
+
let (:env) { double("Vagrant::Environment").as_null_object }
|
4
|
+
let (:vm) { double("Vagrant::VM").as_null_object }
|
5
|
+
let (:ui) { double("Vagrant::UI::Interface").as_null_object }
|
6
|
+
|
7
|
+
before (:each) do
|
8
|
+
env.stub(:primary_vm).and_return vm
|
9
|
+
env.stub(:multivm?).and_return false
|
10
|
+
env.stub(:ui).and_return ui
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { Vagrant::Mirror::Command.new(argv, env) }
|
14
|
+
|
15
|
+
describe "#execute" do
|
16
|
+
shared_examples "cannot run in multivm" do
|
17
|
+
before (:each) do
|
18
|
+
env.stub(:multivm?).and_return(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "throws a SingleVMEnvironmentRequired error" do
|
22
|
+
expect { subject.execute }.to raise_error(Vagrant::Mirror::Errors::SingleVMEnvironmentRequired)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with vagrant mirror sync" do
|
27
|
+
let (:argv) { ['sync' ] }
|
28
|
+
|
29
|
+
it_behaves_like "cannot run in multivm"
|
30
|
+
|
31
|
+
it "runs the sync middleware with primary vm" do
|
32
|
+
vm.should_receive(:run_action).with(Vagrant::Mirror::Middleware::Sync)
|
33
|
+
|
34
|
+
subject.execute
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
shared_examples "the monitor command" do
|
40
|
+
|
41
|
+
it "runs the monitor middleware with primary vm" do
|
42
|
+
vm.should_receive(:run_action).with(Vagrant::Mirror::Middleware::Mirror)
|
43
|
+
|
44
|
+
subject.execute
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with vagrant mirror monitor" do
|
49
|
+
let (:argv) { ['monitor' ] }
|
50
|
+
|
51
|
+
it_behaves_like "cannot run in multivm"
|
52
|
+
it_behaves_like "the monitor command"
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with an empty command" do
|
56
|
+
let (:argv) { [] }
|
57
|
+
|
58
|
+
it_behaves_like "cannot run in multivm"
|
59
|
+
it_behaves_like "the monitor command"
|
60
|
+
end
|
61
|
+
|
62
|
+
shared_examples "the help command" do
|
63
|
+
it "prints valid usage" do
|
64
|
+
ui.should_receive(:info).with(/monitor/, anything())
|
65
|
+
|
66
|
+
subject.execute
|
67
|
+
end
|
68
|
+
|
69
|
+
it "does not run anything" do
|
70
|
+
ui.stub(:info)
|
71
|
+
vm.should_not_receive(:run_action)
|
72
|
+
|
73
|
+
subject.execute
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with unknown command" do
|
78
|
+
let (:argv) { ['nothing'] }
|
79
|
+
|
80
|
+
it_behaves_like "cannot run in multivm"
|
81
|
+
it_behaves_like "the help command"
|
82
|
+
end
|
83
|
+
|
84
|
+
context "with help command" do
|
85
|
+
let (:argv) { ['-h', 'monitor'] }
|
86
|
+
|
87
|
+
it_behaves_like "the help command"
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|