entangler 1.0.0 → 1.1.2
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.
- checksums.yaml +5 -5
- data/Gemfile +2 -0
- data/README.md +7 -5
- data/entangler.gemspec +11 -9
- data/exe/entangler +27 -12
- data/lib/entangler.rb +3 -0
- data/lib/entangler/entangled_file.rb +2 -0
- data/lib/entangler/errors.rb +2 -0
- data/lib/entangler/executor/background/base.rb +10 -5
- data/lib/entangler/executor/background/master.rb +5 -2
- data/lib/entangler/executor/base.rb +9 -16
- data/lib/entangler/executor/helpers.rb +15 -0
- data/lib/entangler/executor/master.rb +20 -57
- data/lib/entangler/executor/slave.rb +3 -1
- data/lib/entangler/executor/validation/base.rb +18 -0
- data/lib/entangler/executor/validation/master.rb +70 -0
- data/lib/entangler/logger.rb +40 -0
- data/lib/entangler/version.rb +3 -1
- metadata +25 -29
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.rubocop.yml +0 -9
- data/.travis.yml +0 -20
- data/Rakefile +0 -8
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 85849db77d8ac84bbb9e7230de8f2d20f88dba32786a029ae0fb06c2700707b6
|
4
|
+
data.tar.gz: 27e90f34ff9c31bc325b907926cf9906144f363d56b361100a62da2ab81a8539
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c57b439657b3314fe8cc44018208e364610321de33939bc9cf34fceb4b0108de61c4385a9aaf7700748f122118af4d62a3fff267461eb912b8cf421103fc4ddd
|
7
|
+
data.tar.gz: 85da07278c48587669f37ed4e8e16abb4474ba55c3fc13a850dc322e1cea66f7934136261bc1302ade69074bbd6d29e9022609693a8db85d0edc48021c729117
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
<p align="center" style="width: 50px;"><img src="https://vignette1.wikia.nocookie.net/pokemon/images/e/ef/114Tangela_Dream.png/revision/latest/scale-to-width-down/185?cb=20141203054028"/></p>
|
2
|
+
<h1 align="center">Entangler</h1>
|
2
3
|
|
3
4
|
[](https://travis-ci.org/daveallie/entangler)
|
4
5
|
|
@@ -18,7 +19,7 @@ $ entangler master /some/base/path user@remote:/some/remote/path
|
|
18
19
|
|
19
20
|
```
|
20
21
|
$ entangler -h
|
21
|
-
Entangler v1.
|
22
|
+
Entangler v1.1.2
|
22
23
|
|
23
24
|
Usage:
|
24
25
|
entangler master <base_dir> <remote_user>@<remote_host>:<remote_base_dir> [options]
|
@@ -30,15 +31,16 @@ Options:
|
|
30
31
|
-p, --port PORT Overwrite the SSH port (usually 22)
|
31
32
|
(doesn't do anything in slave mode)
|
32
33
|
-v, --verbose Log Debug lines
|
34
|
+
-q, --quiet Don't log to stdout in master process
|
33
35
|
--version Show version number
|
34
36
|
-h, --help Show this message
|
35
37
|
```
|
36
38
|
|
37
39
|
### Ignoring files and folders
|
38
40
|
|
39
|
-
If you specify a string, instead of a regex, it will match any path starting with that string, i.e. `-i '.git'` will ignore the `.git`
|
40
|
-
folder and all its sub-directories. If you want to just ignore the the `.git` sub-directories but not the content in the git folder, you'll
|
41
|
-
have to use regex. `-i '/^\.git(?:\/[^\/]+)
|
41
|
+
If you specify a string, instead of a regex, it will match any path starting with that string, i.e. `-i '.git'` will ignore the `.git`
|
42
|
+
folder and all its sub-directories. If you want to just ignore the the `.git` sub-directories but not the content in the git folder, you'll
|
43
|
+
have to use regex. `-i '/^\.git(?:\/[^\/]+){2,}$/'` will match all sub-directories of `.git/`, but not the files in `.git`.
|
42
44
|
|
43
45
|
You can specify multiple `-i` or `--ignore` flags to ignore multiple paths.
|
44
46
|
|
data/entangler.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'entangler/version'
|
5
6
|
|
@@ -14,15 +15,16 @@ Gem::Specification.new do |spec|
|
|
14
15
|
spec.homepage = 'https://github.com/daveallie/entangler'
|
15
16
|
spec.license = 'MIT'
|
16
17
|
|
17
|
-
spec.files =
|
18
|
+
spec.files = Dir['lib/**/*'] + Dir['exe/**/*'] + %w[CODE_OF_CONDUCT.md LICENSE.txt README.md
|
19
|
+
Gemfile entangler.gemspec]
|
18
20
|
spec.bindir = 'exe'
|
19
|
-
spec.executables =
|
21
|
+
spec.executables = ['entangler']
|
20
22
|
spec.require_paths = ['lib']
|
21
23
|
|
22
|
-
spec.add_development_dependency 'bundler', '
|
23
|
-
spec.add_development_dependency 'rake', '
|
24
|
-
spec.add_development_dependency 'rspec', '
|
25
|
-
spec.add_development_dependency 'rubocop', '
|
24
|
+
spec.add_development_dependency 'bundler', '>= 2.1'
|
25
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
26
|
+
spec.add_development_dependency 'rspec', '>= 3.9'
|
27
|
+
spec.add_development_dependency 'rubocop', '>= 0.80'
|
26
28
|
spec.add_dependency 'listen', '~> 3.1'
|
27
|
-
spec.add_dependency 'to_regexp', '~> 0.2'
|
29
|
+
spec.add_dependency 'to_regexp', '~> 0.2.0'
|
28
30
|
end
|
data/exe/entangler
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require 'entangler'
|
3
5
|
require 'optparse'
|
4
6
|
require 'to_regexp'
|
5
7
|
|
8
|
+
def bool_opt(options, opts, key, *args)
|
9
|
+
opts.on(*args) do
|
10
|
+
options[key] = true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_opt(options, opts, key, *args)
|
15
|
+
opts.on(*args) do |val|
|
16
|
+
options[key] = val
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
6
20
|
options = {}
|
7
21
|
OptionParser.new do |opts|
|
8
22
|
opts.banner = %(Entangler v#{Entangler::VERSION}
|
@@ -20,13 +34,10 @@ Usage:
|
|
20
34
|
options[:ignore] << ignore
|
21
35
|
end
|
22
36
|
|
23
|
-
opts
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
opts.on('-v', '--verbose', 'Log Debug lines') do
|
28
|
-
options[:verbose] = true
|
29
|
-
end
|
37
|
+
value_opt(options, opts, :port, '-p', '--port PORT', 'Overwrite the SSH port (usually 22)',
|
38
|
+
"(doesn't do anything in slave mode)")
|
39
|
+
bool_opt(options, opts, :verbose, '-v', '--verbose', 'Log Debug lines')
|
40
|
+
bool_opt(options, opts, :quiet, '-q', '--quiet', "Don't log to stdout in master process")
|
30
41
|
|
31
42
|
opts.on_tail('--version', 'Show version number') do
|
32
43
|
puts Entangler::VERSION
|
@@ -40,7 +51,7 @@ Usage:
|
|
40
51
|
end.parse!
|
41
52
|
|
42
53
|
mode = ARGV.shift
|
43
|
-
unless mode && %w
|
54
|
+
unless mode && %w[master slave].include?(mode)
|
44
55
|
puts "Mode unknown, please read help:\nentangler -h"
|
45
56
|
exit 1
|
46
57
|
end
|
@@ -90,9 +101,7 @@ end
|
|
90
101
|
|
91
102
|
if options[:ignore]
|
92
103
|
opts[:ignore] = options[:ignore].map do |opt|
|
93
|
-
if opt.start_with?('"') && opt.end_with?('"') || opt.start_with?("'") && opt.end_with?("'")
|
94
|
-
opt = opt[1..-2]
|
95
|
-
end
|
104
|
+
opt = opt[1..-2] if opt.start_with?('"') && opt.end_with?('"') || opt.start_with?("'") && opt.end_with?("'")
|
96
105
|
|
97
106
|
if ToRegexp::String.literal? opt
|
98
107
|
source, *rest = opt.as_regexp(detect: true)
|
@@ -103,6 +112,12 @@ if options[:ignore]
|
|
103
112
|
end
|
104
113
|
end
|
105
114
|
|
115
|
+
opts[:quiet] = options[:quiet]
|
106
116
|
opts[:verbose] = options[:verbose]
|
107
117
|
|
108
|
-
|
118
|
+
begin
|
119
|
+
Entangler.run(base_dir, opts)
|
120
|
+
rescue Entangler::EntanglerError => e
|
121
|
+
puts e.message
|
122
|
+
exit 1
|
123
|
+
end
|
data/lib/entangler.rb
CHANGED
data/lib/entangler/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'listen'
|
2
4
|
require 'entangler/entangled_file'
|
3
5
|
require 'benchmark'
|
@@ -56,6 +58,7 @@ module Entangler
|
|
56
58
|
def remove_recently_changed_files(entangled_files)
|
57
59
|
@recently_received_paths.select! { |_, time| Time.now.to_f < time + 0.5 }
|
58
60
|
paths = @recently_received_paths.map(&:first)
|
61
|
+
logger.debug("Skipping paths #{paths.join(", ")} as they have changed recently") if paths.any?
|
59
62
|
entangled_files.reject { |ef| paths.include?(ef.path) }
|
60
63
|
end
|
61
64
|
|
@@ -75,6 +78,7 @@ module Entangler
|
|
75
78
|
def process_remote_changes(changes)
|
76
79
|
with_listener_pause(1) do
|
77
80
|
return if changes.nil?
|
81
|
+
|
78
82
|
logger.info("Processing - #{changes.length} remote changes")
|
79
83
|
logger.debug("File List:\n#{changes.map(&:path).join("\n")}")
|
80
84
|
with_log_time("Completed - #{changes.length} remote changes") do
|
@@ -97,9 +101,9 @@ module Entangler
|
|
97
101
|
|
98
102
|
def with_kill_threads_rescue
|
99
103
|
yield
|
100
|
-
rescue => e
|
101
|
-
|
102
|
-
|
104
|
+
rescue StandardError => e
|
105
|
+
warn e.message
|
106
|
+
warn e.backtrace.join("\n")
|
103
107
|
kill_off_threads
|
104
108
|
end
|
105
109
|
|
@@ -107,8 +111,9 @@ module Entangler
|
|
107
111
|
@listener_pauses[idx] = true
|
108
112
|
listener.pause
|
109
113
|
yield
|
110
|
-
|
111
|
-
|
114
|
+
ensure
|
115
|
+
@listener_pauses[idx] = false
|
116
|
+
listener.start if @listener_pauses.none?
|
112
117
|
end
|
113
118
|
|
114
119
|
def with_log_time(msg)
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
1
5
|
module Entangler
|
2
6
|
module Executor
|
3
7
|
module Background
|
@@ -6,7 +10,6 @@ module Entangler
|
|
6
10
|
|
7
11
|
def start_remote_slave
|
8
12
|
logger.info('Starting - Entangler on remote')
|
9
|
-
require 'open3'
|
10
13
|
ignore_opts = @opts[:ignore].map { |regexp| "-i '#{regexp.inspect}'" }.join(' ')
|
11
14
|
entangler_cmd = "entangler slave #{@opts[:remote_base_dir]} #{ignore_opts}"
|
12
15
|
ssh_cmd = generate_ssh_command("source ~/.rvm/environments/default && #{entangler_cmd}")
|
@@ -20,7 +23,7 @@ module Entangler
|
|
20
23
|
super
|
21
24
|
begin
|
22
25
|
Process.wait @remote_thread[:pid]
|
23
|
-
rescue
|
26
|
+
rescue StandardError
|
24
27
|
nil
|
25
28
|
end
|
26
29
|
end
|
@@ -1,12 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
require 'fileutils'
|
3
|
-
require 'thread'
|
4
5
|
require_relative 'background/base'
|
6
|
+
require_relative 'validation/base'
|
5
7
|
|
6
8
|
module Entangler
|
7
9
|
module Executor
|
8
10
|
class Base
|
9
11
|
include Entangler::Executor::Background::Base
|
12
|
+
include Entangler::Executor::Validation::Base
|
10
13
|
|
11
14
|
attr_reader :base_dir
|
12
15
|
|
@@ -20,6 +23,7 @@ module Entangler
|
|
20
23
|
@opts[:ignore] << /^\.entangler.*/
|
21
24
|
|
22
25
|
validate_opts
|
26
|
+
Entangler::Logger.create_log_dir(base_dir)
|
23
27
|
end
|
24
28
|
|
25
29
|
def generate_abs_path(rel_path)
|
@@ -35,6 +39,7 @@ module Entangler
|
|
35
39
|
start_listener
|
36
40
|
start_remote_io
|
37
41
|
Signal.trap('INT') { kill_off_threads }
|
42
|
+
logger.info('Ready!')
|
38
43
|
wait_for_threads
|
39
44
|
ensure
|
40
45
|
stop_listener
|
@@ -43,28 +48,16 @@ module Entangler
|
|
43
48
|
|
44
49
|
protected
|
45
50
|
|
46
|
-
def validate_opts; end
|
47
|
-
|
48
51
|
def send_to_remote(msg = {})
|
49
52
|
Marshal.dump(msg, @remote_writer)
|
50
53
|
end
|
51
54
|
|
52
55
|
def logger
|
53
|
-
|
54
|
-
@logger ||= begin
|
55
|
-
l = Logger.new(File.join(log_dir, 'entangler.log'))
|
56
|
-
l.level = @opts[:verbose] ? Logger::DEBUG : Logger::INFO
|
57
|
-
l
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def log_dir
|
62
|
-
File.join(base_dir, '.entangler', 'log')
|
56
|
+
@logger ||= Entangler::Logger.new(log_outputs, @opts[:verbose])
|
63
57
|
end
|
64
58
|
|
65
|
-
def
|
66
|
-
|
67
|
-
raise Entangler::ValidationError, 'Base directory is a file' unless File.directory?(base_dir)
|
59
|
+
def log_outputs
|
60
|
+
[Entangler::Logger.log_file_path(base_dir)]
|
68
61
|
end
|
69
62
|
end
|
70
63
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Entangler
|
4
|
+
module Helper
|
5
|
+
def self.with_temp_file(name: 'tmp_file', contents: nil)
|
6
|
+
require 'tempfile'
|
7
|
+
|
8
|
+
t = Tempfile.new(name)
|
9
|
+
t.puts(contents) unless contents.nil?
|
10
|
+
t.close
|
11
|
+
yield t
|
12
|
+
t.unlink
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,9 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helpers'
|
1
4
|
require_relative 'background/master'
|
5
|
+
require_relative 'validation/master'
|
2
6
|
|
3
7
|
module Entangler
|
4
8
|
module Executor
|
5
9
|
class Master < Base
|
6
10
|
include Entangler::Executor::Background::Master
|
11
|
+
include Entangler::Executor::Validation::Master
|
7
12
|
|
8
13
|
def run
|
9
14
|
perform_initial_rsync
|
@@ -16,64 +21,17 @@ module Entangler
|
|
16
21
|
|
17
22
|
private
|
18
23
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
@opts[:remote_port] ||= '22'
|
23
|
-
validate_remote_opts
|
24
|
-
else
|
25
|
-
validate_local_opts
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def validate_local_opts
|
30
|
-
unless File.exist?(@opts[:remote_base_dir])
|
31
|
-
raise Entangler::ValidationError, "Destination directory doesn't exist"
|
32
|
-
end
|
33
|
-
unless File.directory?(@opts[:remote_base_dir])
|
34
|
-
raise Entangler::ValidationError, 'Destination directory is a file'
|
35
|
-
end
|
36
|
-
@opts[:remote_base_dir] = File.realpath(File.expand_path(@opts[:remote_base_dir]))
|
37
|
-
return unless @opts[:remote_base_dir] == base_dir
|
38
|
-
raise Entangler::ValidationError, "Destination directory can't be the same as the base directory"
|
39
|
-
end
|
40
|
-
|
41
|
-
def validate_remote_opts
|
42
|
-
keys = @opts.keys
|
43
|
-
raise Entangler::ValidationError, 'Missing remote base dir' unless keys.include?(:remote_base_dir)
|
44
|
-
raise Entangler::ValidationError, 'Missing remote user' unless keys.include?(:remote_user)
|
45
|
-
raise Entangler::ValidationError, 'Missing remote host' unless keys.include?(:remote_host)
|
46
|
-
validate_remote_base_dir
|
47
|
-
validate_remote_entangler_version
|
48
|
-
end
|
49
|
-
|
50
|
-
def validate_remote_base_dir
|
51
|
-
res = `#{generate_ssh_command("[[ -d '#{@opts[:remote_base_dir]}' ]] && echo 'ok' || echo 'missing'")}`
|
52
|
-
raise Entangler::ValidationError, 'Cannot connect to remote' if res.empty?
|
53
|
-
raise Entangler::ValidationError, 'Remote base dir invalid' unless res.strip == 'ok'
|
54
|
-
end
|
55
|
-
|
56
|
-
def validate_remote_entangler_version
|
57
|
-
return unless @opts[:remote_mode]
|
58
|
-
res = `#{generate_ssh_command('source ~/.rvm/environments/default && entangler --version')}`
|
59
|
-
remote_version = Gem::Version.new(res.strip)
|
60
|
-
local_version = Gem::Version.new(Entangler::VERSION)
|
61
|
-
return unless major_version_mismatch?(local_version, remote_version)
|
62
|
-
msg = 'Entangler version too far apart, please update either local or remote Entangler.' \
|
63
|
-
" Local version is #{local_version} and remote version is #{remote_version}."
|
64
|
-
raise Entangler::VersionMismatchError, msg
|
65
|
-
end
|
66
|
-
|
67
|
-
def major_version_mismatch?(version1, version2)
|
68
|
-
version1.segments[0] != version2.segments[0] ||
|
69
|
-
(version1.segments[0].zero? && version1 != version2) ||
|
70
|
-
((version1.prerelease? || version2.prerelease?) && version1 != version2)
|
24
|
+
def log_outputs
|
25
|
+
outs = [Entangler::Logger.log_file_path(base_dir)]
|
26
|
+
outs << STDOUT unless @opts[:quiet]
|
71
27
|
end
|
72
28
|
|
73
29
|
def perform_initial_rsync
|
74
30
|
logger.info 'Running initial sync'
|
75
|
-
|
76
|
-
|
31
|
+
with_temp_rsync_ignores do |file_path|
|
32
|
+
IO.popen(rsync_cmd_string(file_path)).each do |line|
|
33
|
+
logger.debug line.chomp
|
34
|
+
end
|
77
35
|
end
|
78
36
|
logger.debug 'Initial sync complete'
|
79
37
|
end
|
@@ -107,12 +65,17 @@ module Entangler
|
|
107
65
|
"#{@opts[:remote_user]}@#{@opts[:remote_host]}"
|
108
66
|
end
|
109
67
|
|
110
|
-
def
|
111
|
-
|
68
|
+
def with_temp_rsync_ignores
|
69
|
+
Entangler::Helper.with_temp_file(name: 'rsync_ignores', contents: find_rsync_ignore_folders.join("\n")) do |f|
|
70
|
+
yield f.path
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def rsync_cmd_string(rsync_ignores_file_path)
|
112
75
|
remote_path = @opts[:remote_mode] ? "#{@opts[:remote_user]}@#{@opts[:remote_host]}:" : ''
|
113
76
|
remote_path += "#{@opts[:remote_base_dir]}/"
|
114
77
|
|
115
|
-
cmd = "rsync -azv #{
|
78
|
+
cmd = "rsync -azv --exclude-from #{rsync_ignores_file_path}"
|
116
79
|
cmd += " -e \"ssh -p #{@opts[:remote_port]}\"" if @opts[:remote_mode]
|
117
80
|
cmd + " --delete #{base_dir}/ #{remote_path}"
|
118
81
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Entangler
|
2
4
|
module Executor
|
3
5
|
class Slave < Base
|
@@ -10,7 +12,7 @@ module Entangler
|
|
10
12
|
|
11
13
|
@remote_reader = STDIN
|
12
14
|
@remote_writer = STDOUT
|
13
|
-
$stderr.reopen(File.join(
|
15
|
+
$stderr.reopen(File.join(Entangler::Logger.log_file_path(base_dir, 'entangler.err')), 'w')
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Entangler
|
4
|
+
module Executor
|
5
|
+
module Validation
|
6
|
+
module Base
|
7
|
+
protected
|
8
|
+
|
9
|
+
def validate_opts; end
|
10
|
+
|
11
|
+
def validate_base_dir(base_dir)
|
12
|
+
raise Entangler::ValidationError, "Base directory doesn't exist" unless File.exist?(base_dir)
|
13
|
+
raise Entangler::ValidationError, 'Base directory is a file' unless File.directory?(base_dir)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Entangler
|
4
|
+
module Executor
|
5
|
+
module Validation
|
6
|
+
module Master
|
7
|
+
private
|
8
|
+
|
9
|
+
def validate_opts
|
10
|
+
super
|
11
|
+
if @opts[:remote_mode]
|
12
|
+
@opts[:remote_port] ||= '22'
|
13
|
+
validate_remote_opts
|
14
|
+
else
|
15
|
+
validate_local_opts
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate_local_opts
|
20
|
+
unless File.exist?(@opts[:remote_base_dir])
|
21
|
+
raise Entangler::ValidationError, "Destination directory doesn't exist"
|
22
|
+
end
|
23
|
+
unless File.directory?(@opts[:remote_base_dir])
|
24
|
+
raise Entangler::ValidationError, 'Destination directory is a file'
|
25
|
+
end
|
26
|
+
|
27
|
+
@opts[:remote_base_dir] = File.realpath(File.expand_path(@opts[:remote_base_dir]))
|
28
|
+
return unless @opts[:remote_base_dir] == base_dir
|
29
|
+
|
30
|
+
raise Entangler::ValidationError, "Destination directory can't be the same as the base directory"
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_remote_opts
|
34
|
+
keys = @opts.keys
|
35
|
+
raise Entangler::ValidationError, 'Missing remote base dir' unless keys.include?(:remote_base_dir)
|
36
|
+
raise Entangler::ValidationError, 'Missing remote user' unless keys.include?(:remote_user)
|
37
|
+
raise Entangler::ValidationError, 'Missing remote host' unless keys.include?(:remote_host)
|
38
|
+
|
39
|
+
validate_remote_base_dir
|
40
|
+
validate_remote_entangler_version
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_remote_base_dir
|
44
|
+
res = `#{generate_ssh_command("[[ -d '#{@opts[:remote_base_dir]}' ]] && echo 'ok' || echo 'missing'")}`
|
45
|
+
raise Entangler::ValidationError, 'Cannot connect to remote' if res.empty?
|
46
|
+
raise Entangler::ValidationError, 'Remote base dir invalid' unless res.strip == 'ok'
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_remote_entangler_version
|
50
|
+
return unless @opts[:remote_mode]
|
51
|
+
|
52
|
+
res = `#{generate_ssh_command('source ~/.rvm/environments/default && entangler --version')}`
|
53
|
+
remote_version = Gem::Version.new(res.strip)
|
54
|
+
local_version = Gem::Version.new(Entangler::VERSION)
|
55
|
+
return unless major_version_mismatch?(local_version, remote_version)
|
56
|
+
|
57
|
+
msg = 'Entangler version too far apart, please update either local or remote Entangler.' \
|
58
|
+
" Local version is #{local_version} and remote version is #{remote_version}."
|
59
|
+
raise Entangler::VersionMismatchError, msg
|
60
|
+
end
|
61
|
+
|
62
|
+
def major_version_mismatch?(version1, version2)
|
63
|
+
version1.segments[0] != version2.segments[0] ||
|
64
|
+
(version1.segments[0].zero? && version1 != version2) ||
|
65
|
+
((version1.prerelease? || version2.prerelease?) && version1 != version2)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Entangler
|
7
|
+
class Logger
|
8
|
+
def self.create_log_dir(base_dir)
|
9
|
+
FileUtils.mkdir_p(File.dirname(log_file_path(base_dir)))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.log_file_path(base_dir, log_file_name = 'entangler.log')
|
13
|
+
File.join(base_dir, '.entangler', 'log', log_file_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(outputs, verbose = false)
|
17
|
+
@loggers = Array(outputs).map do |output|
|
18
|
+
logger = ::Logger.new(output)
|
19
|
+
|
20
|
+
logger.level = verbose ? ::Logger::DEBUG : ::Logger::INFO
|
21
|
+
logger.formatter = proc do |severity, datetime, _, msg|
|
22
|
+
date_format = datetime.strftime('%Y-%m-%d %H:%M:%S')
|
23
|
+
"[#{date_format}] #{severity.rjust(5)}: #{msg}\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
logger
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def level=(level)
|
31
|
+
@loggers.each { |logger| logger.level = level }
|
32
|
+
end
|
33
|
+
|
34
|
+
::Logger::Severity.constants.each do |level|
|
35
|
+
define_method(level.downcase) do |*args|
|
36
|
+
@loggers.each { |logger| logger.send(level.downcase, *args) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/entangler/version.rb
CHANGED
metadata
CHANGED
@@ -1,71 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: entangler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave Allie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
19
|
+
version: '2.1'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '2.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 12.3.3
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 12.3.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
47
|
+
version: '3.9'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
54
|
+
version: '3.9'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rubocop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
61
|
+
version: '0.80'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0.
|
68
|
+
version: '0.80'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: listen
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 0.2.0
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 0.2.0
|
97
97
|
description: Two way file syncer using platform native notify.
|
98
98
|
email:
|
99
99
|
- dave@daveallie.com
|
@@ -102,17 +102,10 @@ executables:
|
|
102
102
|
extensions: []
|
103
103
|
extra_rdoc_files: []
|
104
104
|
files:
|
105
|
-
- ".gitignore"
|
106
|
-
- ".rspec"
|
107
|
-
- ".rubocop.yml"
|
108
|
-
- ".travis.yml"
|
109
105
|
- CODE_OF_CONDUCT.md
|
110
106
|
- Gemfile
|
111
107
|
- LICENSE.txt
|
112
108
|
- README.md
|
113
|
-
- Rakefile
|
114
|
-
- bin/console
|
115
|
-
- bin/setup
|
116
109
|
- entangler.gemspec
|
117
110
|
- exe/entangler
|
118
111
|
- lib/entangler.rb
|
@@ -121,8 +114,12 @@ files:
|
|
121
114
|
- lib/entangler/executor/background/base.rb
|
122
115
|
- lib/entangler/executor/background/master.rb
|
123
116
|
- lib/entangler/executor/base.rb
|
117
|
+
- lib/entangler/executor/helpers.rb
|
124
118
|
- lib/entangler/executor/master.rb
|
125
119
|
- lib/entangler/executor/slave.rb
|
120
|
+
- lib/entangler/executor/validation/base.rb
|
121
|
+
- lib/entangler/executor/validation/master.rb
|
122
|
+
- lib/entangler/logger.rb
|
126
123
|
- lib/entangler/version.rb
|
127
124
|
homepage: https://github.com/daveallie/entangler
|
128
125
|
licenses:
|
@@ -143,8 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
140
|
- !ruby/object:Gem::Version
|
144
141
|
version: '0'
|
145
142
|
requirements: []
|
146
|
-
|
147
|
-
rubygems_version: 2.4.8
|
143
|
+
rubygems_version: 3.1.2
|
148
144
|
signing_key:
|
149
145
|
specification_version: 4
|
150
146
|
summary: Two way file syncer using platform native notify.
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.rubocop.yml
DELETED
data/.travis.yml
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
sudo: false
|
2
|
-
language: ruby
|
3
|
-
os:
|
4
|
-
- linux
|
5
|
-
- osx
|
6
|
-
rvm:
|
7
|
-
- 2.3.3
|
8
|
-
- ruby-head
|
9
|
-
matrix:
|
10
|
-
include:
|
11
|
-
- os: osx
|
12
|
-
rvm: 2.2.5
|
13
|
-
- os: linux
|
14
|
-
rvm: 2.2.6
|
15
|
-
allow_failures:
|
16
|
-
- rvm: ruby-head
|
17
|
-
before_install:
|
18
|
-
- gem install bundler -v 1.13.6
|
19
|
-
install:
|
20
|
-
- bundle install --jobs=3 --retry=3
|
data/Rakefile
DELETED
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'bundler/setup'
|
4
|
-
require 'entangler'
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require 'irb'
|
14
|
-
IRB.start
|