vagrant-mirror 0.1.0.alpha
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.
- 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
|