vagrant-vaimo-unison 0.0.1

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: 665a6ac67e4fccd7dc7277186858488fbe94214f
4
+ data.tar.gz: a42e74a68dcd28324af7c9a0e01dbe97d7c76380
5
+ SHA512:
6
+ metadata.gz: d1681956f1bdd042532d52f5229e265a4d5c6592f04ef58cafd610b422f9c570c11ef826b708602483e88ed1619168f556b4d5e2753e9c9ee772a91cba067fa4
7
+ data.tar.gz: 9a71bcbfb28214619e303b7085317db6ebaec8d0b6d55b90fe9f1e5bc6992dfdad3ad65eaf245d20f6a58700c809ebc36b88c0d893a51f6cc50d4023e8dc5d91
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ # OS-specific
2
+ .DS_Store
3
+
4
+ # Bundler/Rubygems
5
+ *.gem
6
+ .bundle
7
+ pkg/*
8
+ tags
9
+ Gemfile.lock
10
+
11
+ # Vagrant
12
+ .vagrant
13
+ Vagrantfile
14
+ !example_box/Vagrantfile
15
+
16
+ # IDEs
17
+ .project
18
+ .idea
19
+ .tags*
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # 1.0.2 (Feb 2016)
2
+ * fix the cleanup command to delete contents of directory in Vagrant but not the directory itself, because deleteing the directory can mess up e.g. a docker container that has that directory mounted.
3
+
4
+ # 1.0.1
5
+ * Small bugfix release
6
+
7
+ # 1.0.0 (Jan 2016)
8
+
9
+ * Breaking commit on dcosson fork
10
+ * Renames all of the `vagrant ___` commands to start with `unison`, e.g. `vagrant-unison-sync`
11
+ * Adds a command to sync once and exit, `vagrant unison-sync-once`
12
+ * Fix bug where cleanup tried to delete `~/.unison` as root, which resolved to wrong thing (at least in Virtualbox) and failed silently bc what we want to delete is `/home/vagrant/.unison`
13
+ * Pin to newer syntax of the listen gem and stop using a method that had been renamed.
14
+
15
+ # 0.0.17 (Jan 2016)
16
+
17
+ * Fix bug in validation. Previously, you couldn't run vagrant on a Vagrantfile that didn't use vagrant-unison if you had the plugin installed, because all the config args were required.
18
+
19
+ # 0.1.0 (March 2013)
20
+
21
+ * Initial release.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ # We depend on Vagrant for development, but we don't add it as a
7
+ # gem dependency because we expect to be installed within the
8
+ # Vagrant environment itself using `vagrant plugin`.
9
+ gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git"
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2013 Mitchell Hashimoto
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # Vagrant Unison 2 Plugin
2
+
3
+ This is a [Vagrant](http://www.vagrantup.com) 1.7+ plugin that syncs files over SSH from a local folder
4
+ to your Vagrant VM (local or on AWS). Under the covers it uses [Unison](http://www.cis.upenn.edu/~bcpierce/unison/)
5
+
6
+ **NOTE:** This plugin requires Vagrant 1.7+,
7
+
8
+ ## Features
9
+
10
+ * Unisoned folder support via `unison` over `ssh` -> will work with any vagrant provider, eg Virtualbox or AWS, though it's most tested on a local VM via Virtualbox.
11
+
12
+ ## Usage
13
+
14
+ 1. You must already have [Unison](http://www.cis.upenn.edu/~bcpierce/unison/) installed on your path on your host and guest machines, and it must be the same version of Unison on both.
15
+ * On Mac you can install this with Homebrew: `brew install unison`
16
+ * This will install unison 2.48.3
17
+ * On Ubuntu:
18
+ * [Xenial (16.04)](https://launchpad.net/ubuntu/xenial/+source/unison): `sudo apt-get install unison`
19
+ * Ubuntu Trusty (14.04):
20
+ * `sudo add-apt-repository ppa:eugenesan/ppa`
21
+ * `sudo apt-get update`
22
+ * `sudo apt-get install unison=2.48.3-1ubuntu1.02~eugenesan~trusty2`
23
+ * Other 64-bit Linux:
24
+ * Install package from `http://ftp5.gwdg.de/pub/linux/archlinux/extra/os/x86_64/unison-2.48.3-2-x86_64.pkg.tar.xz`. (Install at your own risk, this is a plain http link. If someone knows of a signed version, checksum, or https host let me know so I can update it).
25
+ * On Windows, download [2.48.3](http://www.pps.univ-paris-diderot.fr/~vouillon/unison/unison 2.48.3.zip), unzip, rename `unison-2.48.3 text.exe` to `unison.exe` and copy to somewhere in your path. Alternatively, install [using Chocolatey](https://chocolatey.org/packages/unison).
26
+
27
+ 2. Install using standard Vagrant 1.1+ plugin installation methods.
28
+ * `vagrant plugin install vagrant-vaimo-unison`
29
+
30
+ 3. Configure unison in your Vagrantfile, as shown below.
31
+
32
+ 4. Start your vagrant box as normal (eg: `vagrant up`)
33
+
34
+ ## Configuration
35
+
36
+ ```ruby
37
+ Vagrant.configure("2") do |config|
38
+ config.vm.box = "dummy"
39
+
40
+ # Required configs
41
+ config.unison.host_folder = "src/" #relative to the folder your Vagrantfile is in
42
+ config.unison.guest_folder = "src/" #relative to the vagrant home folder (e.g. /home/vagrant)
43
+
44
+ # Optional configs
45
+ # File patterns to ignore when syncing. Ensure you don't have spaces between the commas!
46
+ config.unison.ignore = "Name {.DS_Store,.git,node_modules}" # Default: none
47
+
48
+ # SSH connection details for Vagrant to communicate with VM.
49
+ config.unison.ssh_host = "10.0.0.1" # Default: '127.0.0.1'
50
+ config.unison.ssh_port = 22 # Default: 2222
51
+ config.unison.ssh_user = "deploy" # Default: 'vagrant'
52
+ config.unison.perms = 0 # if you get "properties changed on both sides" error
53
+
54
+ # `vagrant unison-sync-polling` command will restart unison in VM if memory
55
+ # usage gets above this threshold (in MB).
56
+ config.unison.mem_cap_mb = 500 # Default: 200
57
+
58
+ # Change polling interval (in seconds) at which to sync changes
59
+ config.unison.repeat = 5 # Default: 1
60
+ end
61
+ ```
62
+
63
+
64
+ ## Start syncing Folders
65
+
66
+ Run `vagrant unison-sync-once` to run a single, non-interactive sync and then exit.
67
+
68
+ Run `vagrant unison-sync-polling` to start in bidirect monitor (repeat) mode - every second unison checks for changes on either side and syncs them.
69
+
70
+ ## (Legacy) Sync using OSX inotify events
71
+
72
+ Run `vagrant unison-sync` to sync then start watching the local_folder for changes, and syncing these to your vagrang VM.
73
+
74
+ This uses the `watch` ruby gem and runs a one-off unison sync when it gets change events.
75
+
76
+ For some reason, this does not always get all the events immediately which can be frustrating. Since the polling mode (unison repeat mode) is not too resource intensive, I recommend that instead.
77
+
78
+ ## Sync in interactive mode
79
+
80
+ Run `vagrant unison-sync-interactive` to start in interactive mode. The first time
81
+ it will ask what to do for every top-level file & directory, otherwise is asks
82
+ about changes. It allows solving conflicts in various ways. Press "?" in
83
+ interactive mode to see options for resolving.
84
+
85
+ This is a useful tool when the automatic sync sees a change in a file on both
86
+ sides and skips it.
87
+
88
+ ## Cleanup unison database
89
+ Run `vagrant unison-cleanup` to clear the unison metadata from `~/Library/Application Support/Unison/` as well as all files.
90
+
91
+ ## Error states
92
+
93
+ ### Inconsistent state with unison metadata
94
+
95
+ When you get
96
+ ```
97
+ Fatal error: Warning: inconsistent state.
98
+ The archive file is missing on some hosts.
99
+ For safety, the remaining copies should be deleted.
100
+ Archive arb126d8de1ef26a835b94cf51975c530f on host blablabla.local should be DELETED
101
+ Archive arbc6a36f85b3d1473c55565dd220acf68 on host blablabla is MISSING
102
+ Please delete archive files as appropriate and try again
103
+ or invoke Unison with -ignorearchives flag.
104
+ ```
105
+
106
+ You should run a unison-cleanup
107
+
108
+ Running Unison with -ignorearchives flag is a bad idea, since it will produce conflicts.
109
+
110
+ ### Uncaught exception `bad bigarray kind`
111
+
112
+ The error is:
113
+ ```
114
+ Unison failed: Uncaught exception Failure("input_value: bad bigarray kind")
115
+ ```
116
+
117
+ This is caused when the unison on your host and guest were compiled with different versions of ocaml. To fix ensure that
118
+ both are compiled with the same ocaml version. [More Info Here](https://gist.github.com/pch/aa1c9c4ec8522a11193b)
119
+
120
+ ### Skipping files changed on both sides
121
+
122
+ This is most often caused in my experience by files that get changed by different users with different permissions.
123
+
124
+ For instance, if you're running an Ubuntu VM then the unison process is running
125
+ as the vagrant user. If you have the unison synced folder loaded as a volume in
126
+ a docker container and a new file gets created in the container, the vagrant
127
+ user in the VM won't own that file and this can keep unison from being able to
128
+ sync the file. (I'm looking for a way to fix this particular error case).
129
+
130
+ Running a unison-cleanup should fix this state.
131
+
132
+ ### Properties changed on both sides
133
+
134
+ The error is:
135
+ ```
136
+ Synchronization complete at 13:00:33 (0 item transferred, 1 skipped, 0 failed)
137
+ skipped: (properties changed on both sides)
138
+ props <-?-> props /
139
+ ```
140
+
141
+ This can be caused by file permissions being synced. If you get this error set
142
+ the perms arg to 0 as in the [example configuration](#configuration).
143
+
144
+ See [perms documentation](http://www.cis.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html#perms) for more info.
145
+
146
+
147
+ ## Development
148
+
149
+ To work on the `vagrant-unison` plugin, clone this repository out, and use
150
+ [Bundler](http://gembundler.com) to get the dependencies:
151
+
152
+ ```
153
+ $ bundle
154
+ ```
155
+
156
+ Once you have the dependencies, verify the unit tests pass with `rake`:
157
+
158
+ ```
159
+ $ bundle exec rake
160
+ ```
161
+
162
+ If those pass, you're ready to start developing the plugin. You can test
163
+ the plugin without installing it into your Vagrant environment by just
164
+ creating a `Vagrantfile` in the top level of this directory (it is gitignored)
165
+ that uses it, and uses bundler to execute Vagrant:
166
+
167
+ ```
168
+ $ bundle exec vagrant up
169
+ $ bundle exec vagrant unison-sync
170
+ ```
171
+
172
+ Or, install the plugin from your local build to use with an existing project's
173
+ Vagrantfile on your machine.
174
+
175
+ Build the plugin with
176
+
177
+ ```
178
+ rake build
179
+ ```
180
+
181
+ Now you'll see the built gem in a pkg directory. Install it with
182
+
183
+ ```
184
+ vagrant plugin install pkg/vagrant-unison-VERSION.gem
185
+ ```
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rspec/core/rake_task'
4
+
5
+ # Immediately sync all stdout so that tools like buildbot can
6
+ # immediately load in the output.
7
+ $stdout.sync = true
8
+ $stderr.sync = true
9
+
10
+ # Change to the directory of this file.
11
+ Dir.chdir(File.expand_path("../", __FILE__))
12
+
13
+ # This installs the tasks that help with gem creation and
14
+ # publishing.
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ # Install the `spec` task so that we can run tests.
18
+ RSpec::Core::RakeTask.new
19
+
20
+ # Default task is to run the unit tests
21
+ task :default => "spec"
@@ -0,0 +1,168 @@
1
+ require "log4r"
2
+ require "vagrant"
3
+ require "thread"
4
+
5
+ require_relative 'unison_paths'
6
+ require_relative 'ssh_command'
7
+ require_relative 'shell_command'
8
+ require_relative 'unison_sync'
9
+
10
+ module VagrantPlugins
11
+ module Unison
12
+ class CommandOnce < Vagrant.plugin("2", :command)
13
+ include UnisonSync
14
+
15
+ def self.synopsis
16
+ "sync the unison shared folder once"
17
+ end
18
+
19
+ def execute
20
+ status = nil
21
+ with_target_vms do |machine|
22
+ execute_sync_command(machine) do |command|
23
+ command.batch = true
24
+ command.terse = true
25
+ command = command.to_s
26
+
27
+ @env.ui.info "Running unison once"
28
+ @env.ui.info " #{command}"
29
+
30
+ status = system(command)
31
+ @env.ui.info "**** unison exited. success: #{status} ****"
32
+ end
33
+ end
34
+ if status
35
+ return 0
36
+ else
37
+ return 1
38
+ end
39
+ end
40
+ end
41
+
42
+ class CommandPolling < Vagrant.plugin("2", :command)
43
+ include UnisonSync
44
+ attr_accessor :bg_thread
45
+
46
+ def self.synopsis
47
+ "sync the unison shared folder forever, by polling for changes"
48
+ end
49
+
50
+ def execute
51
+ status = nil
52
+ with_target_vms do |machine|
53
+ @bg_thread = watch_vm_for_memory_leak(machine)
54
+ execute_sync_command(machine) do |command|
55
+ command.repeat = true
56
+ command.terse = true
57
+ command = command.to_s
58
+
59
+ @env.ui.info "Running #{command}"
60
+
61
+ # Re-run on a crash.
62
+ # On a sigint, wait 2 seconds before respawning command.
63
+ # If INT comes in again while waiting, program exits.
64
+ # If INT comes in after we've respanwned,
65
+ # will bring us back to this trap handler.
66
+ exit_on_next_sigint = false
67
+ while true
68
+ begin
69
+ sleep 2 if exit_on_next_sigint
70
+ exit_on_next_sigint = false
71
+ status = system(command)
72
+ @env.ui.info "**** unison exited. success: #{status} ****"
73
+ rescue Interrupt
74
+ if exit_on_next_sigint
75
+ Thread.kill(@bg_thread) if @bg_thread
76
+ exit 1
77
+ end
78
+ @env.ui.info '** Hit Ctrl + C again to kill. **'
79
+ exit_on_next_sigint = true
80
+ rescue Exception
81
+ @env.ui.info '** Sync crashed. Respawning. Hit Ctrl + C twice to kill. **'
82
+ end
83
+ end
84
+ end
85
+ end
86
+ if status
87
+ return 0
88
+ else
89
+ return 1
90
+ end
91
+ end
92
+
93
+ def watch_vm_for_memory_leak(machine)
94
+ ssh_command = SshCommand.new(machine)
95
+ Thread.new(ssh_command.ssh, machine.config.unison.mem_cap_mb) do |ssh_command_text, mem_cap_mb|
96
+ while true
97
+ sleep 15
98
+ total_mem = `#{ssh_command_text} 'free -m | egrep "^Mem:" | awk "{print \\$2}"' 2>/dev/null`
99
+ _unison_proc_returnval = (
100
+ `#{ssh_command_text} 'ps aux | grep "[u]nison -server" | awk "{print \\$2, \\$4}"' 2>/dev/null`
101
+ )
102
+ if _unison_proc_returnval == ''
103
+ puts "Unison not running in VM"
104
+ next
105
+ end
106
+ pid, mem_pct_unison = _unison_proc_returnval.strip.split(' ')
107
+ mem_unison = (total_mem.to_f * mem_pct_unison.to_f/100).round(1)
108
+ # Debugging: uncomment to log every loop tick
109
+ # puts "Unison running as #{pid} using #{mem_unison} mb"
110
+ if mem_unison > mem_cap_mb
111
+ puts "Unison using #{mem_unison}MB memory is over limit of #{mem_cap_mb}MB, restarting"
112
+ `#{ssh_command_text} kill -HUP #{pid} 2>/dev/null`
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ class CommandCleanup < Vagrant.plugin("2", :command)
120
+ def self.synopsis
121
+ "remove all unison supporting state on local and remote system"
122
+ end
123
+
124
+ def execute
125
+ with_target_vms do |machine|
126
+ guest_path = UnisonPaths.new(@env, machine).guest
127
+
128
+ command = "rm -rf ~/Library/'Application Support'/Unison/*"
129
+ @env.ui.info "Running #{command} on host"
130
+ system(command)
131
+
132
+ command = "rm -rf #{guest_path}/* #{guest_path}/..?* #{guest_path}/.[!.]*"
133
+ @env.ui.info "Running #{command} on guest VM (delete all files from directory including hidden ones)"
134
+ machine.communicate.sudo(command)
135
+
136
+ command = "rm -rf ~/.unison"
137
+ @env.ui.info "Running #{command} on guest VM"
138
+ machine.communicate.execute(command)
139
+ end
140
+
141
+ 0
142
+ end
143
+ end
144
+
145
+ class CommandInteract < Vagrant.plugin("2", :command)
146
+ include UnisonSync
147
+
148
+ def self.synopsis
149
+ "run unison in interactive mode, to resolve conflicts"
150
+ end
151
+
152
+ def execute
153
+ with_target_vms do |machine|
154
+ execute_sync_command(machine) do |command|
155
+ command.terse = true
156
+ command = command.to_s
157
+
158
+ @env.ui.info "Running #{command}"
159
+
160
+ system(command)
161
+ end
162
+ end
163
+
164
+ 0
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,100 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module Unison
5
+ class Config < Vagrant.plugin("2", :config)
6
+ # Host Folder to Sync
7
+ #
8
+ # @return [String]
9
+ attr_accessor :host_folder
10
+
11
+ # Guest Folder to Sync.
12
+ #
13
+ # @return [String]
14
+ attr_accessor :guest_folder
15
+
16
+ # Pattern of files to ignore.
17
+ #
18
+ # @return [String, Array<String>]
19
+ attr_accessor :ignore
20
+
21
+ # Repeat speed.
22
+ #
23
+ # @return [String]
24
+ attr_accessor :repeat
25
+
26
+ # SSH host.
27
+ #
28
+ # @return [String]
29
+ attr_accessor :ssh_host
30
+
31
+ # SSH port.
32
+ #
33
+ # @return [String]
34
+ attr_accessor :ssh_port
35
+
36
+ # SSH user.
37
+ #
38
+ # @return [String]
39
+ attr_accessor :ssh_user
40
+
41
+ # Memory usage cap in MB
42
+ # Restart Unison in the VM when it's consuming more than this
43
+ # amount of memory (in MB)
44
+ # @return [int]
45
+ attr_accessor :mem_cap_mb
46
+
47
+ # perms arg value
48
+ #
49
+ # @return [int]
50
+ attr_accessor :perms
51
+
52
+ # Airlab-specific config option to leave off the Vagrant identity file, so
53
+ # SSH will just use ssh-agent
54
+ #
55
+ # @return [Boolean]
56
+ attr_accessor :ssh_use_agent
57
+
58
+ def initialize(region_specific = false)
59
+ @host_folder = UNSET_VALUE
60
+ @guest_folder = UNSET_VALUE
61
+ @ignore = UNSET_VALUE
62
+ @repeat = UNSET_VALUE
63
+ @ssh_host = UNSET_VALUE
64
+ @ssh_port = UNSET_VALUE
65
+ @ssh_user = UNSET_VALUE
66
+ @ssh_use_agent = UNSET_VALUE
67
+ @mem_cap_mb = UNSET_VALUE
68
+ @perms = UNSET_VALUE
69
+ end
70
+
71
+ def finalize!
72
+ # The access keys default to nil
73
+ @host_folder = nil if @host_folder == UNSET_VALUE
74
+ @guest_folder = nil if @guest_folder == UNSET_VALUE
75
+ @ignore = nil if @ignore == UNSET_VALUE
76
+ @repeat = 1 if @repeat == UNSET_VALUE
77
+ @ssh_host = '127.0.0.1' if @ssh_host == UNSET_VALUE
78
+ @ssh_port = 2222 if @ssh_port == UNSET_VALUE
79
+ @ssh_user = 'vagrant' if @ssh_user == UNSET_VALUE
80
+ @mem_cap_mb = 200 if @mem_cap_mb == UNSET_VALUE
81
+ @perms = nil if @perms == UNSET_VALUE
82
+ @ssh_use_agent = false if @ssh_use_agent == UNSET_VALUE
83
+
84
+ # Mark that we finalized
85
+ @__finalized = true
86
+ end
87
+
88
+ def validate(machine)
89
+ errors = []
90
+
91
+ if !(@host_folder.nil? && @guest_folder.nil?)
92
+ errors << I18n.t("vagrant_unison.config.unison_host_folder_required") if @host_folder.nil?
93
+ errors << I18n.t("vagrant_unison.config.unison_guest_folder_required") if @guest_folder.nil?
94
+ end
95
+
96
+ { "Unison" => errors }
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,9 @@
1
+ require "vagrant"
2
+
3
+ module Vagrant
4
+ module Errors
5
+ class UnisonError < VagrantError
6
+ error_key(:unison_error, "vagrant_unison.errors")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,103 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "The vagrant-unison plugin must be run within Vagrant."
5
+ end
6
+
7
+ # This is a sanity check to make sure no one is attempting to install
8
+ # this into an early Vagrant version.
9
+ if Vagrant::VERSION < "1.1.0"
10
+ raise "The vagrant-unison plugin is only compatible with Vagrant 1.1+"
11
+ end
12
+
13
+ module VagrantPlugins
14
+ module Unison
15
+ class Plugin < Vagrant.plugin("2")
16
+ name "Unison"
17
+ description <<-DESC
18
+ This plugin syncs files over SSH from a local folder
19
+ to your Vagrant VM (local or on AWS).
20
+ DESC
21
+
22
+ config "unison" do
23
+ require_relative "config"
24
+ Config
25
+ end
26
+
27
+ command "unison-sync-once" do
28
+ setup_logging
29
+ setup_i18n
30
+
31
+ #Return the command
32
+ require_relative "command"
33
+ CommandOnce
34
+ end
35
+
36
+ command "unison-sync-interact" do
37
+ # Setup logging and i18n
38
+ setup_logging
39
+ setup_i18n
40
+
41
+ #Return the command
42
+ require_relative "command"
43
+ CommandInteract
44
+ end
45
+
46
+
47
+ command "unison-sync-polling" do
48
+ # Setup logging and i18n
49
+ setup_logging
50
+ setup_i18n
51
+
52
+ #Return the command
53
+ require_relative "command"
54
+ CommandPolling
55
+ end
56
+
57
+ command "unison-cleanup" do
58
+ # Setup logging and i18n
59
+ setup_logging
60
+ setup_i18n
61
+
62
+ #Return the command
63
+ require_relative "command"
64
+ CommandCleanup
65
+ end
66
+
67
+ # This initializes the internationalization strings.
68
+ def self.setup_i18n
69
+ I18n.load_path << File.expand_path("locales/en.yml", Unison.source_root)
70
+ I18n.reload!
71
+ end
72
+
73
+ # This sets up our log level to be whatever VAGRANT_LOG is.
74
+ def self.setup_logging
75
+ require "log4r"
76
+
77
+ level = nil
78
+ begin
79
+ level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
80
+ rescue NameError
81
+ # This means that the logging constant wasn't found,
82
+ # which is fine. We just keep `level` as `nil`. But
83
+ # we tell the user.
84
+ level = nil
85
+ end
86
+
87
+ # Some constants, such as "true" resolve to booleans, so the
88
+ # above error checking doesn't catch it. This will check to make
89
+ # sure that the log level is an integer, as Log4r requires.
90
+ level = nil if !level.is_a?(Integer)
91
+
92
+ # Set the logging level on all "vagrant" namespaced
93
+ # logs as long as we have a valid level.
94
+ if level
95
+ logger = Log4r::Logger.new("vagrant_unison")
96
+ logger.outputters = Log4r::Outputter.stderr
97
+ logger.level = level
98
+ logger = nil
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,117 @@
1
+ module VagrantPlugins
2
+ module Unison
3
+ class ShellCommand
4
+ def initialize(machine, unison_paths, ssh_command)
5
+ @machine = machine
6
+ @unison_paths = unison_paths
7
+ @ssh_command = ssh_command
8
+ end
9
+
10
+ attr_accessor :batch, :repeat, :terse
11
+ attr_accessor :force_remote, :force_local
12
+ attr_accessor :prefer_remote, :prefer_local
13
+
14
+ def to_a
15
+ args.map do |arg|
16
+ arg = arg[1...-1] if arg =~ /\A"(.*)"\z/
17
+ arg
18
+ end
19
+ end
20
+
21
+ def to_s
22
+ args.join(' ')
23
+ end
24
+
25
+ private
26
+
27
+ def args
28
+ _args = [
29
+ 'unison',
30
+ local_root_arg,
31
+ remote_root_arg,
32
+ batch_arg,
33
+ terse_arg,
34
+ repeat_arg,
35
+ ignore_arg,
36
+ perms_arg,
37
+ force_arg,
38
+ prefer_arg,
39
+ ssh_args,
40
+ ].flatten.compact
41
+ _args
42
+ end
43
+
44
+ def local_root_arg
45
+ @unison_paths.host
46
+ end
47
+
48
+ def remote_root_arg
49
+ @ssh_command.uri(@unison_paths)
50
+ end
51
+
52
+ def ssh_args
53
+ ['-sshargs', %("#{@ssh_command.ssh_args}")]
54
+ end
55
+
56
+ def batch_arg
57
+ '-batch' if batch
58
+ end
59
+
60
+ def ignore_arg
61
+ patterns = []
62
+ if @machine.config.unison.ignore.is_a? ::Array
63
+ patterns += @machine.config.unison.ignore
64
+ elsif @machine.config.unison.ignore
65
+ patterns << @machine.config.unison.ignore
66
+ end
67
+
68
+ patterns.map do |pattern|
69
+ ['-ignore', %("#{pattern}")]
70
+ end
71
+ end
72
+
73
+ def perms_arg
74
+ ['-perms', @machine.config.unison.perms] if @machine.config.unison.perms
75
+ end
76
+
77
+ def repeat_arg
78
+ ['-repeat', @machine.config.unison.repeat] if repeat && @machine.config.unison.repeat
79
+ end
80
+
81
+ def terse_arg
82
+ '-terse' if terse
83
+ end
84
+
85
+ # from the docs:
86
+ #
87
+ # Including the preference -force root causes Unison to resolve all
88
+ # differences (even non-conflicting changes) in favor of root. This
89
+ # effectively changes Unison from a synchronizer into a mirroring
90
+ # utility. You can also specify -force newer (or -force older) to force
91
+ # Unison to choose the file with the later (earlier) modtime. In this
92
+ # case, the -times preference must also be enabled. This preference is
93
+ # overridden by the forcepartial preference. This preference should be
94
+ # used only if you are sure you know what you are doing!
95
+ #
96
+ # soo. I'm not sure if I know what I'm doing. Need to make sure that this
97
+ # doesn't end up deleting .git or some other ignored but critical
98
+ # directory.
99
+ def force_arg
100
+ return ['-force', local_root_arg] if force_local
101
+ return ['-force', remote_root_arg] if force_remote
102
+ end
103
+
104
+ # from the docs, via Daniel Low (thx daniel):
105
+ #
106
+ # Including the preference -prefer root causes Unison always to resolve
107
+ # conflicts in favor of root, rather than asking for guidance from the
108
+ # user.
109
+ #
110
+ # This is much safer than -force
111
+ def prefer_arg
112
+ return ['-prefer', local_root_arg] if prefer_local
113
+ return ['-prefer', remote_root_arg] if prefer_remote
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,54 @@
1
+ module VagrantPlugins
2
+ module Unison
3
+ class SshCommand
4
+ def initialize(machine)
5
+ @machine = machine
6
+ end
7
+
8
+ def ssh
9
+ %W(
10
+ ssh
11
+ #{@machine.config.unison.ssh_user}@#{@machine.config.unison.ssh_host}
12
+ #{ssh_args}
13
+ ).compact.join(' ')
14
+ end
15
+
16
+ def ssh_args
17
+ %W(
18
+ -p #{@machine.config.unison.ssh_port}
19
+ #{proxy_command}
20
+ -o StrictHostKeyChecking=no
21
+ -o UserKnownHostsFile=/dev/null
22
+ #{identity}
23
+ ).compact.join(' ')
24
+ end
25
+
26
+ def uri(unison_paths)
27
+ username = @machine.config.unison.ssh_user
28
+ host = @machine.config.unison.ssh_host
29
+
30
+ "ssh://#{username}@#{host}/#{unison_paths.guest}"
31
+ end
32
+
33
+ private
34
+
35
+ def proxy_command
36
+ command = @machine.ssh_info[:proxy_command]
37
+ return nil unless command
38
+ "-o ProxyCommand='#{command}'"
39
+ end
40
+
41
+ def identity
42
+ if @machine.config.unison.ssh_use_agent
43
+ ''
44
+ else
45
+ (%w(-o IdentitiesOnly=yes) << key_paths).join(' ')
46
+ end
47
+ end
48
+
49
+ def key_paths
50
+ @machine.ssh_info[:private_key_path].map { |p| "-i #{p.shellescape}" }.join(' ')
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ module VagrantPlugins
2
+ module Unison
3
+ class UnisonPaths
4
+ def initialize(env, machine)
5
+ @env = env
6
+ @machine = machine
7
+ end
8
+
9
+ def guest
10
+ @machine.config.unison.guest_folder
11
+ end
12
+
13
+ def host
14
+ @host ||= begin
15
+ path = File.expand_path(@machine.config.unison.host_folder, @env.root_path)
16
+
17
+ # Make sure there is a trailing slash on the host path to
18
+ # avoid creating an additional directory with rsync
19
+ path = "#{path}/" if path !~ /\/$/
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,99 @@
1
+ require "optparse"
2
+
3
+ module VagrantPlugins
4
+ module Unison
5
+ # mixin providing common functionality for our vagrant commands
6
+ module UnisonSync
7
+ def execute_sync_command(machine)
8
+ parse_options!
9
+
10
+ return unless machine.config.unison.host_folder
11
+
12
+ unison_paths = UnisonPaths.new(@env, machine)
13
+ guest_path = unison_paths.guest
14
+ host_path = unison_paths.host
15
+
16
+ @env.ui.info "Unisoning changes from {host}::#{host_path} --> {guest VM}::#{guest_path}"
17
+
18
+ # Create the guest path
19
+ machine.communicate.sudo("mkdir -p '#{guest_path}'")
20
+ machine.communicate.sudo("chown #{machine.config.unison.ssh_user} '#{guest_path}'")
21
+
22
+ ssh_command = SshCommand.new(machine)
23
+ shell_command = ShellCommand.new(machine, unison_paths, ssh_command)
24
+
25
+ shell_command.prefer_local = options[:prefer_local]
26
+ shell_command.prefer_remote = options[:prefer_remote]
27
+ shell_command.force_local = options[:force_local]
28
+ shell_command.force_remote = options[:force_remote]
29
+
30
+ yield shell_command
31
+ end
32
+
33
+ def parse_options!
34
+ # parse_options(option_parser) is provided by vagrant, but
35
+ # documentation is scarse. Best way to view the docs (imo) is to put
36
+ # a binding.pry in here and then type `? parse_options`
37
+ @parsed_argv ||= parse_options(options_parser)
38
+
39
+ if options[:verbose]
40
+ @env.ui.info "Options: #{options}"
41
+ end
42
+
43
+ # According to the docs:
44
+ # > If parse_options returns `nil`, then you should assume that
45
+ # > help was printed and parsing failed.
46
+ if @parsed_argv == nil
47
+ exit 1
48
+ end
49
+ end
50
+
51
+ def options
52
+ @options ||= {
53
+ :prefer_local => false,
54
+ :prefer_remote => false,
55
+ :force_local => false,
56
+ :force_remote => false,
57
+ :verbose => false,
58
+ }
59
+ end
60
+
61
+ def options_parser
62
+ @option_parser ||= OptionParser.new do |o|
63
+ o.banner = "Usage: vagrant #{ARGV[0]} [options]"
64
+
65
+ o.on('--push', 'prefer changes on the local machine.') do |flag|
66
+ options[:prefer_local] = flag
67
+ check_conflicting_options!
68
+ end
69
+
70
+ o.on('--pull', 'prefer changes on the remote machine.') do |flag|
71
+ options[:prefer_remote] = flag
72
+ check_conflicting_options!
73
+ end
74
+
75
+ o.on('--force-push', 'force-push changes to the remote machine. Dangerous!') do |flag|
76
+ options[:force_local] = flag
77
+ check_conflicting_options!
78
+ end
79
+
80
+ o.on('--force-pull', 'force-pull changes from the remote machine. Super dangerous!') do |flag|
81
+ options[:force_remote] = flag
82
+ check_conflicting_options!
83
+ end
84
+
85
+ o.on('--verbose', 'Print additional debug information') do |flag|
86
+ options[:verbose] = flag
87
+ end
88
+ end
89
+ end
90
+
91
+ def check_conflicting_options!
92
+ enabled = [:prefer_local, :prefer_remote, :force_local, :force_remote].select do |opt|
93
+ options[opt]
94
+ end
95
+ raise ArgumentError.new("Conflicting options: #{enabled.inspect}") if enabled.length > 1
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,5 @@
1
+ module VagrantPlugins
2
+ module Unison
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ require "pathname"
2
+
3
+ require "vagrant-vaimo-unison/plugin"
4
+ require "vagrant-vaimo-unison/errors"
5
+
6
+ module VagrantPlugins
7
+ module Unison
8
+ # This returns the path to the source of this plugin.
9
+ #
10
+ # @return [Pathname]
11
+ def self.source_root
12
+ @source_root ||= Pathname.new(File.expand_path("../../", __FILE__))
13
+ end
14
+ end
15
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,19 @@
1
+ en:
2
+ vagrant_unison:
3
+
4
+ config:
5
+ host_folder_required: |-
6
+ Host folder is required
7
+ guest_folder_required: |-
8
+ Guest folder is required
9
+
10
+ errors:
11
+ unison_error: |-
12
+ There was an error when attemping to sync folders using unison.
13
+ Please inspect the error message below for more info.
14
+
15
+ Host path: %{hostpath}
16
+ Guest path: %{guestpath}
17
+ Error: %{stderr}
18
+ Full command causing error:
19
+ %{command}
@@ -0,0 +1,32 @@
1
+ require "vagrant-vaimo-unison/config"
2
+
3
+ describe VagrantPlugins::Unison::Config do
4
+ let(:instance) { described_class.new }
5
+
6
+ describe "defaults" do
7
+ subject do
8
+ instance.tap do |o|
9
+ o.finalize!
10
+ end
11
+ end
12
+
13
+ its("host_folder") { should be_nil }
14
+ its("guest_folder") { should be_nil }
15
+ its("ignore") { should be_nil }
16
+ end
17
+
18
+ describe "overriding defaults" do
19
+ # I typically don't meta-program in tests, but this is a very
20
+ # simple boilerplate test, so I cut corners here. It just sets
21
+ # each of these attributes to "foo" in isolation, and reads the value
22
+ # and asserts the proper result comes back out.
23
+ [:host_folder, :guest_folder].each do |attribute|
24
+
25
+ it "should not default #{attribute} if overridden" do
26
+ instance.send("#{attribute}=".to_sym, "foo")
27
+ instance.finalize!
28
+ instance.send(attribute).should == "foo"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ $:.unshift File.expand_path("../lib", __FILE__)
2
+ require "vagrant-vaimo-unison/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "vagrant-vaimo-unison"
6
+ s.version = VagrantPlugins::Unison::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["David Laing", "dmatora", "Danny Cosson"]
9
+ s.email = "dcosson@gmail.com"
10
+ s.homepage = "http://github.com/dcosson/vagrant-unison"
11
+ s.summary = "Vagrant 1.7+ plugin to sync local files to VM over SSH using Unison"
12
+ s.description = "Vagrant 1.7+ plugin to sync local files to VM over SSH using Unison"
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+
16
+ s.add_development_dependency 'rake', '< 11.0'
17
+ s.add_development_dependency "rspec-core", "~> 2.12.2"
18
+ s.add_development_dependency "rspec-expectations", "~> 2.12.1"
19
+ s.add_development_dependency "rspec-mocks", "~> 2.12.1"
20
+
21
+ # The following block of code determines the files that should be included
22
+ # in the gem. It does this by reading all the files in the directory where
23
+ # this gemspec is, and parsing out the ignored files from the gitignore.
24
+ # Note that the entire gitignore(5) syntax is not supported, specifically
25
+ # the "!" syntax, but it should mostly work correctly.
26
+ root_path = File.dirname(__FILE__)
27
+ all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") }
28
+ all_files.reject! { |file| [".", ".."].include?(File.basename(file)) }
29
+ gitignore_path = File.join(root_path, ".gitignore")
30
+ gitignore = File.readlines(gitignore_path)
31
+ gitignore.map! { |line| line.chomp.strip }
32
+ gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ }
33
+
34
+ unignored_files = all_files.reject do |file|
35
+ # Ignore any directories, the gemspec only cares about files
36
+ next true if File.directory?(file)
37
+
38
+ # Ignore any paths that match anything in the gitignore. We do
39
+ # two tests here:
40
+ #
41
+ # - First, test to see if the entire path matches the gitignore.
42
+ # - Second, match if the basename does, this makes it so that things
43
+ # like '.DS_Store' will match sub-directories too (same behavior
44
+ # as git).
45
+ #
46
+ gitignore.any? do |ignore|
47
+ File.fnmatch(ignore, file, File::FNM_PATHNAME) ||
48
+ File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME)
49
+ end
50
+ end
51
+
52
+ s.files = unignored_files
53
+ s.executables = unignored_files.map { |f| f[/^bin\/(.*)/, 1] }.compact
54
+ s.require_path = 'lib'
55
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-vaimo-unison
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Laing
8
+ - dmatora
9
+ - Danny Cosson
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2017-06-07 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '11.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "<"
27
+ - !ruby/object:Gem::Version
28
+ version: '11.0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rspec-core
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: 2.12.2
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: 2.12.2
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec-expectations
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 2.12.1
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: 2.12.1
57
+ - !ruby/object:Gem::Dependency
58
+ name: rspec-mocks
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 2.12.1
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 2.12.1
71
+ description: Vagrant 1.7+ plugin to sync local files to VM over SSH using Unison
72
+ email: dcosson@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - CHANGELOG.md
79
+ - Gemfile
80
+ - LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - lib/vagrant-vaimo-unison.rb
84
+ - lib/vagrant-vaimo-unison/command.rb
85
+ - lib/vagrant-vaimo-unison/config.rb
86
+ - lib/vagrant-vaimo-unison/errors.rb
87
+ - lib/vagrant-vaimo-unison/plugin.rb
88
+ - lib/vagrant-vaimo-unison/shell_command.rb
89
+ - lib/vagrant-vaimo-unison/ssh_command.rb
90
+ - lib/vagrant-vaimo-unison/unison_paths.rb
91
+ - lib/vagrant-vaimo-unison/unison_sync.rb
92
+ - lib/vagrant-vaimo-unison/version.rb
93
+ - locales/en.yml
94
+ - spec/vagrant-unison/config_spec.rb
95
+ - vagrant-vaimo-unison.gemspec
96
+ homepage: http://github.com/dcosson/vagrant-unison
97
+ licenses: []
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 1.3.6
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.6.12
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Vagrant 1.7+ plugin to sync local files to VM over SSH using Unison
119
+ test_files: []