Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented repeat mode #1

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ Vagrantfile

# IDEs
.project
.idea
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ to your Vagrant VM (local or on AWS). Under the covers it uses [Unison](http://
## Usage

1. You must already have [Unison](http://www.cis.upenn.edu/~bcpierce/unison/) installed and in your path.
* On Mac you can install this with Homebrew: `brew install unison`
* On Mac you can install this with Homebrew: `brew install unison` (on Yosemite you will have to use https://rudix-mountainlion.googlecode.com/files/unison-2.40.102-0.pkg)
* On Unix (Ubuntu) install using `sudo apt-get install unison`
* On Windows, download [2.40.102](http://alan.petitepomme.net/unison/assets/Unison-2.40.102.zip), unzip, rename `Unison-2.40.102 Text.exe` to `unison.exe` and copy to somewhere in your path.
1. Install using standard Vagrant 1.1+ plugin installation methods.
Expand All @@ -26,6 +26,7 @@ Vagrant.configure("2") do |config|

config.sync.host_folder = "src/" #relative to the folder your Vagrantfile is in
config.sync.guest_folder = "src/" #relative to the vagrant home folder -> /home/vagrant
config.sync.ignore = "Name {.idea,.DS_Store}"

end
```
Expand All @@ -38,6 +39,29 @@ Run `vagrant sync` to start watching the local_folder for changes, and syncing t
Under the covers this uses your system installation of [Unison](http://www.cis.upenn.edu/~bcpierce/unison/),
which must be installed in your path.

## Start syncing Folders in repeat mode

Run `vagrant sync-repeat` to start in bidirect monitor (repeat) mode.

## Start syncing Folders in interactive mode

Run `vagrant sync-interact` to start in interactive mode that allows solving conflicts.

## Cleanup unison database
When you get
```
Fatal error: Warning: inconsistent state.
The archive file is missing on some hosts.
For safety, the remaining copies should be deleted.
Archive arb126d8de1ef26a835b94cf51975c530f on host blablabla.local should be DELETED
Archive arbc6a36f85b3d1473c55565dd220acf68 on host blablabla is MISSING
Please delete archive files as appropriate and try again
or invoke Unison with -ignorearchives flag.
```

Run `vagrant sync-cleanup` to clear Archive from ~/Library/Application Support/Unison/ and files from host folder.
Running Unison with -ignorearchives flag is a bad idea, since it will produce conflicts.

## Development

To work on the `vagrant-unison` plugin, clone this repository out, and use
Expand Down
151 changes: 92 additions & 59 deletions lib/vagrant-unison/command.rb
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,35 @@
require "thread"
require 'listen'

require_relative 'unison_paths'
require_relative 'ssh_command'
require_relative 'shell_command'
require_relative 'unison_sync'

module VagrantPlugins
module Unison
class Command < Vagrant.plugin("2", :command)

include UnisonSync

def execute

with_target_vms do |machine|
hostpath, guestpath = init_paths machine
paths = UnisonPaths.new(@env, machine)
host_path = paths.host

trigger_unison_sync machine
sync(machine, paths)

@env.ui.info "Watching #{hostpath} for changes..."
@env.ui.info "Watching #{host_path} for changes..."

listener = Listen.to(hostpath) do |modified, added, removed|
listener = Listen.to(host_path) do |modified, added, removed|
@env.ui.info "Detected modifications to #{modified.inspect}" unless modified.empty?
@env.ui.info "Detected new files #{added.inspect}" unless added.empty?
@env.ui.info "Detected deleted files #{removed.inspect}" unless removed.empty?
trigger_unison_sync machine

sync(machine, paths)
end

queue = Queue.new

callback = lambda do
# This needs to execute in another thread because Thread
# synchronization can't happen in a trap context.
Expand All @@ -40,70 +47,96 @@ def execute
end
end

0 #all is well
0
end

def sync(machine, paths)
execute_sync_command(machine) do |command|
command.batch = true

@env.ui.info "Running #{command.to_s}"

r = Vagrant::Util::Subprocess.execute(*command.to_a)

case r.exit_code
when 0
@env.ui.info "Unison completed succesfully"
when 1
@env.ui.info "Unison completed - all file transfers were successful; some files were skipped"
when 2
@env.ui.info "Unison completed - non-fatal failures during file transfer: #{r.stderr}"
else
raise Vagrant::Errors::UnisonError,
:command => command.to_s,
:guestpath => paths.guest,
:hostpath => paths.host,
:stderr => r.stderr
end
end
end
end

def init_paths(machine)
hostpath = File.expand_path(machine.config.sync.host_folder, @env.root_path)
guestpath = machine.config.sync.guest_folder
class CommandRepeat < Vagrant.plugin("2", :command)
include UnisonSync

# Make sure there is a trailing slash on the host path to
# avoid creating an additional directory with rsync
hostpath = "#{hostpath}/" if hostpath !~ /\/$/
def execute
with_target_vms do |machine|
execute_sync_command(machine) do |command|
command.repeat = true
command.terse = true
command = command.to_s

@env.ui.info "Running #{command}"

system(command)
end
end

[hostpath, guestpath]
0
end
end

def trigger_unison_sync(machine)
hostpath, guestpath = init_paths machine
class CommandCleanup < Vagrant.plugin("2", :command)
include UnisonSync

@env.ui.info "Unisoning changes from {host}::#{hostpath} --> {guest VM}::#{guestpath}"
def execute
with_target_vms do |machine|
guest_path = UnisonPaths.new(@env, machine).guest

ssh_info = machine.ssh_info
command = "rm -rf ~/Library/'Application Support'/Unison/*"
@env.ui.info "Running #{command} on host"
system(command)

# Create the guest path
machine.communicate.sudo("mkdir -p '#{guestpath}'")
machine.communicate.sudo("chown #{ssh_info[:username]} '#{guestpath}'")
command = "rm -rf #{guest_path}"
@env.ui.info "Running #{command} on guest VM"
machine.communicate.sudo(command)

proxy_command = ""
if ssh_info[:proxy_command]
proxy_command = "-o ProxyCommand='#{ssh_info[:proxy_command]}' "
command = "rm -rf ~/.unison"
@env.ui.info "Running #{command} on guest VM"
machine.communicate.sudo(command)
end

rsh = [
"-p #{ssh_info[:port]} " +
proxy_command +
"-o StrictHostKeyChecking=no " +
"-o UserKnownHostsFile=/dev/null",
ssh_info[:private_key_path].map { |p| "-i '#{p}'" },
].flatten.join(" ")

# Unison over to the guest path using the SSH info
command = [
"unison", "-batch",
"-ignore=Name {.git*,.vagrant/,*.DS_Store}",
"-sshargs", rsh,
hostpath,
"ssh://#{ssh_info[:username]}@#{ssh_info[:host]}/#{guestpath}"
]

r = Vagrant::Util::Subprocess.execute(*command)
case r.exit_code
when 0
@env.ui.info "Unison completed succesfully"
when 1
@env.ui.info "Unison completed - all file transfers were successful; some files were skipped"
when 2
@env.ui.info "Unison completed - non-fatal failures during file transfer"
else
raise Vagrant::Errors::UnisonError,
:command => command.inspect,
:guestpath => guestpath,
:hostpath => hostpath,
:stderr => r.stderr
0
end
end

class CommandInteract < Vagrant.plugin("2", :command)
include UnisonSync

def execute
with_target_vms do |machine|
execute_sync_command(machine) do |command|
command.terse = true
command = command.to_s

@env.ui.info "Running #{command}"

system(command)
end
end

0
end

end
end
end
end
18 changes: 16 additions & 2 deletions lib/vagrant-unison/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,31 @@
module VagrantPlugins
module Unison
class Config < Vagrant.plugin("2", :config)
# The access key ID for accessing AWS.
# Host Folder to Sync
#
# @return [String]
attr_accessor :host_folder

# The ID of the AMI to use.
# Guest Folder to Sync.
#
# @return [String]
attr_accessor :guest_folder

# Pattern of files to ignore.
#
# @return [String]
attr_accessor :ignore

# Repeat speed.
#
# @return [String]
attr_accessor :repeat

def initialize(region_specific=false)
@host_folder = UNSET_VALUE
@remote_folder = UNSET_VALUE
@ignore = UNSET_VALUE
@repeat = UNSET_VALUE
end

#-------------------------------------------------------------------
Expand All @@ -34,6 +46,8 @@ def finalize!
# The access keys default to nil
@host_folder = nil if @host_folder == UNSET_VALUE
@guest_folder = nil if @guest_folder == UNSET_VALUE
@ignore = nil if @ignore == UNSET_VALUE
@repeat = 1 if @repeat == UNSET_VALUE

# Mark that we finalized
@__finalized = true
Expand Down
30 changes: 30 additions & 0 deletions lib/vagrant-unison/plugin.rb
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,36 @@ class Plugin < Vagrant.plugin("2")
Command
end

command "sync-repeat" do
# Setup logging and i18n
setup_logging
setup_i18n

#Return the command
require_relative "command"
CommandRepeat
end

command "sync-cleanup" do
# Setup logging and i18n
setup_logging
setup_i18n

#Return the command
require_relative "command"
CommandCleanup
end

command "sync-interact" do
# Setup logging and i18n
setup_logging
setup_i18n

#Return the command
require_relative "command"
CommandInteract
end

# This initializes the internationalization strings.
def self.setup_i18n
I18n.load_path << File.expand_path("locales/en.yml", Unison.source_root)
Expand Down
55 changes: 55 additions & 0 deletions lib/vagrant-unison/shell_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module VagrantPlugins
module Unison
class ShellCommand
def initialize machine, paths, ssh_command
@machine = machine
@paths = paths
@ssh_command = ssh_command
end

attr_accessor :batch, :repeat, :terse

def to_a
args.map do |arg|
arg = arg[1...-1] if arg =~ /\A"(.*)"\z/
arg
end
end

def to_s
args.join(' ')
end

private

def args
[
'unison',
@paths.host,
@ssh_command.uri,
batch_arg,
terse_arg,
repeat_arg,
ignore_arg,
['-sshargs', %("#{@ssh_command.command}")],
].flatten.compact
end

def batch_arg
'-batch' if batch
end

def ignore_arg
['-ignore', %("#{@machine.config.sync.ignore}")] if @machine.config.sync.ignore
end

def repeat_arg
['-repeat', @machine.config.sync.repeat] if repeat && @machine.config.sync.repeat
end

def terse_arg
'-terse' if terse
end
end
end
end
Loading