sshkit 1.7.1 → 1.8.0
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 +4 -4
- data/.travis.yml +2 -2
- data/BREAKING_API_WISHLIST.md +14 -0
- data/CHANGELOG.md +74 -0
- data/CONTRIBUTING.md +43 -0
- data/EXAMPLES.md +265 -169
- data/Gemfile +7 -0
- data/README.md +274 -9
- data/RELEASING.md +16 -8
- data/Rakefile +8 -0
- data/lib/sshkit.rb +0 -9
- data/lib/sshkit/all.rb +6 -4
- data/lib/sshkit/backends/abstract.rb +42 -42
- data/lib/sshkit/backends/connection_pool.rb +57 -8
- data/lib/sshkit/backends/local.rb +21 -50
- data/lib/sshkit/backends/netssh.rb +45 -98
- data/lib/sshkit/backends/printer.rb +3 -23
- data/lib/sshkit/backends/skipper.rb +4 -8
- data/lib/sshkit/color.rb +51 -20
- data/lib/sshkit/command.rb +68 -47
- data/lib/sshkit/configuration.rb +38 -5
- data/lib/sshkit/deprecation_logger.rb +17 -0
- data/lib/sshkit/formatters/abstract.rb +28 -4
- data/lib/sshkit/formatters/black_hole.rb +1 -2
- data/lib/sshkit/formatters/dot.rb +3 -10
- data/lib/sshkit/formatters/pretty.rb +31 -56
- data/lib/sshkit/formatters/simple_text.rb +6 -44
- data/lib/sshkit/host.rb +5 -6
- data/lib/sshkit/logger.rb +0 -1
- data/lib/sshkit/mapping_interaction_handler.rb +47 -0
- data/lib/sshkit/runners/parallel.rb +1 -1
- data/lib/sshkit/runners/sequential.rb +1 -1
- data/lib/sshkit/version.rb +1 -1
- data/sshkit.gemspec +0 -1
- data/test/functional/backends/test_local.rb +14 -1
- data/test/functional/backends/test_netssh.rb +58 -50
- data/test/helper.rb +2 -2
- data/test/unit/backends/test_abstract.rb +145 -0
- data/test/unit/backends/test_connection_pool.rb +27 -2
- data/test/unit/backends/test_printer.rb +47 -47
- data/test/unit/formatters/test_custom.rb +65 -0
- data/test/unit/formatters/test_dot.rb +25 -32
- data/test/unit/formatters/test_pretty.rb +114 -22
- data/test/unit/formatters/test_simple_text.rb +83 -0
- data/test/unit/test_color.rb +69 -5
- data/test/unit/test_command.rb +53 -18
- data/test/unit/test_command_map.rb +0 -4
- data/test/unit/test_configuration.rb +47 -7
- data/test/unit/test_coordinator.rb +45 -52
- data/test/unit/test_deprecation_logger.rb +38 -0
- data/test/unit/test_host.rb +3 -4
- data/test/unit/test_logger.rb +0 -1
- data/test/unit/test_mapping_interaction_handler.rb +101 -0
- metadata +37 -41
- data/lib/sshkit/utils/capture_output_methods.rb +0 -13
- data/test/functional/test_coordinator.rb +0 -17
@@ -1,5 +1,19 @@
|
|
1
1
|
require "thread"
|
2
2
|
|
3
|
+
# Since we call to_s on new_connection_args and use that as a hash
|
4
|
+
# We need to make sure the memory address of the object is not used as part of the key
|
5
|
+
# Otherwise identical objects with different memory address won't get a hash hit.
|
6
|
+
# In the case of proxy commands, this can lead to proxy processes leaking
|
7
|
+
# And in severe cases can cause deploys to fail due to default file descriptor limits
|
8
|
+
# An alternate solution would be to use a different means of generating hash keys
|
9
|
+
module Net; module SSH; module Proxy
|
10
|
+
class Command
|
11
|
+
def inspect
|
12
|
+
@command_line_template
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end;end;end
|
16
|
+
|
3
17
|
module SSHKit
|
4
18
|
|
5
19
|
module Backend
|
@@ -15,18 +29,34 @@ module SSHKit
|
|
15
29
|
end
|
16
30
|
|
17
31
|
def checkout(*new_connection_args, &block)
|
18
|
-
|
32
|
+
entry = nil
|
19
33
|
key = new_connection_args.to_s
|
20
|
-
|
21
|
-
|
22
|
-
|
34
|
+
if idle_timeout
|
35
|
+
prune_expired
|
36
|
+
entry = find_live_entry(key)
|
37
|
+
end
|
38
|
+
entry || create_new_entry(new_connection_args, key, &block)
|
23
39
|
end
|
24
40
|
|
25
41
|
def checkin(entry)
|
26
|
-
|
42
|
+
if idle_timeout
|
43
|
+
prune_expired
|
44
|
+
entry.expires_at = Time.now + idle_timeout
|
45
|
+
@mutex.synchronize do
|
46
|
+
@pool[entry.key] ||= []
|
47
|
+
@pool[entry.key] << entry
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def close_connections
|
27
53
|
@mutex.synchronize do
|
28
|
-
@pool
|
29
|
-
|
54
|
+
@pool.values.flatten.map(&:connection).uniq.each do |conn|
|
55
|
+
if conn.respond_to?(:closed?) && conn.respond_to?(:close)
|
56
|
+
conn.close unless conn.closed?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@pool.clear
|
30
60
|
end
|
31
61
|
end
|
32
62
|
|
@@ -36,10 +66,25 @@ module SSHKit
|
|
36
66
|
|
37
67
|
private
|
38
68
|
|
69
|
+
def prune_expired
|
70
|
+
@mutex.synchronize do
|
71
|
+
@pool.each_value do |entries|
|
72
|
+
entries.collect! do |entry|
|
73
|
+
if entry.expired?
|
74
|
+
entry.close unless entry.closed?
|
75
|
+
nil
|
76
|
+
else
|
77
|
+
entry
|
78
|
+
end
|
79
|
+
end.compact!
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
39
84
|
def find_live_entry(key)
|
40
85
|
@mutex.synchronize do
|
41
86
|
return nil unless @pool.key?(key)
|
42
|
-
while entry = @pool[key].shift
|
87
|
+
while (entry = @pool[key].shift)
|
43
88
|
return entry if entry.live?
|
44
89
|
end
|
45
90
|
end
|
@@ -61,6 +106,10 @@ module SSHKit
|
|
61
106
|
expires_at && Time.now > expires_at
|
62
107
|
end
|
63
108
|
|
109
|
+
def close
|
110
|
+
connection.respond_to?(:close) && connection.close
|
111
|
+
end
|
112
|
+
|
64
113
|
def closed?
|
65
114
|
connection.respond_to?(:closed?) && connection.closed?
|
66
115
|
end
|
@@ -4,35 +4,14 @@ module SSHKit
|
|
4
4
|
|
5
5
|
module Backend
|
6
6
|
|
7
|
-
class Local <
|
7
|
+
class Local < Abstract
|
8
8
|
|
9
9
|
def initialize(_ = nil, &block)
|
10
10
|
@host = Host.new(:local) # just for logging
|
11
11
|
@block = block
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
instance_exec(@host, &@block)
|
16
|
-
end
|
17
|
-
|
18
|
-
def test(*args)
|
19
|
-
options = args.extract_options!.merge(
|
20
|
-
raise_on_non_zero_exit: false,
|
21
|
-
verbosity: Logger::DEBUG
|
22
|
-
)
|
23
|
-
_execute(*[*args, options]).success?
|
24
|
-
end
|
25
|
-
|
26
|
-
def execute(*args)
|
27
|
-
_execute(*args).success?
|
28
|
-
end
|
29
|
-
|
30
|
-
def capture(*args)
|
31
|
-
options = { verbosity: Logger::DEBUG }.merge(args.extract_options!)
|
32
|
-
_execute(*[*args, options]).full_stdout
|
33
|
-
end
|
34
|
-
|
35
|
-
def upload!(local, remote, options = {})
|
14
|
+
def upload!(local, remote, _options = {})
|
36
15
|
if local.is_a?(String)
|
37
16
|
FileUtils.cp(local, remote)
|
38
17
|
else
|
@@ -42,7 +21,7 @@ module SSHKit
|
|
42
21
|
end
|
43
22
|
end
|
44
23
|
|
45
|
-
def download!(remote, local=nil,
|
24
|
+
def download!(remote, local=nil, _options = {})
|
46
25
|
if local.nil?
|
47
26
|
FileUtils.cp(remote, File.basename(remote))
|
48
27
|
else
|
@@ -54,40 +33,32 @@ module SSHKit
|
|
54
33
|
|
55
34
|
private
|
56
35
|
|
57
|
-
def
|
58
|
-
|
59
|
-
output << cmd
|
60
|
-
|
61
|
-
cmd.started = Time.now
|
36
|
+
def execute_command(cmd)
|
37
|
+
output.log_command_start(cmd)
|
62
38
|
|
63
|
-
|
64
|
-
stdout_thread = Thread.new do
|
65
|
-
while line = stdout.gets do
|
66
|
-
cmd.stdout = line
|
67
|
-
cmd.full_stdout += line
|
39
|
+
cmd.started = Time.now
|
68
40
|
|
69
|
-
|
70
|
-
|
41
|
+
Open3.popen3(cmd.to_command) do |stdin, stdout, stderr, wait_thr|
|
42
|
+
stdout_thread = Thread.new do
|
43
|
+
while (line = stdout.gets) do
|
44
|
+
cmd.on_stdout(stdin, line)
|
45
|
+
output.log_command_data(cmd, :stdout, line)
|
71
46
|
end
|
47
|
+
end
|
72
48
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
output << cmd
|
79
|
-
end
|
49
|
+
stderr_thread = Thread.new do
|
50
|
+
while (line = stderr.gets) do
|
51
|
+
cmd.on_stderr(stdin, line)
|
52
|
+
output.log_command_data(cmd, :stderr, line)
|
80
53
|
end
|
54
|
+
end
|
81
55
|
|
82
|
-
|
83
|
-
|
56
|
+
stdout_thread.join
|
57
|
+
stderr_thread.join
|
84
58
|
|
85
|
-
|
86
|
-
cmd.stdout = ''
|
87
|
-
cmd.stderr = ''
|
59
|
+
cmd.exit_status = wait_thr.value.to_i
|
88
60
|
|
89
|
-
|
90
|
-
end
|
61
|
+
output.log_command_exit(cmd)
|
91
62
|
end
|
92
63
|
end
|
93
64
|
|
@@ -15,29 +15,9 @@ end
|
|
15
15
|
|
16
16
|
module SSHKit
|
17
17
|
|
18
|
-
class Logger
|
19
|
-
|
20
|
-
class Net::SSH::LogLevelShim
|
21
|
-
attr_reader :output
|
22
|
-
def initialize(output)
|
23
|
-
@output = output
|
24
|
-
end
|
25
|
-
def debug(args)
|
26
|
-
output << LogMessage.new(Logger::TRACE, args)
|
27
|
-
end
|
28
|
-
def error(args)
|
29
|
-
output << LogMessage.new(Logger::ERROR, args)
|
30
|
-
end
|
31
|
-
def lwarn(args)
|
32
|
-
output << LogMessage.new(Logger::WARN, args)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
18
|
module Backend
|
39
19
|
|
40
|
-
class Netssh <
|
20
|
+
class Netssh < Abstract
|
41
21
|
|
42
22
|
class Configuration
|
43
23
|
attr_accessor :connection_timeout, :pty
|
@@ -48,35 +28,6 @@ module SSHKit
|
|
48
28
|
end
|
49
29
|
end
|
50
30
|
|
51
|
-
include SSHKit::CommandHelper
|
52
|
-
|
53
|
-
def run
|
54
|
-
instance_exec(host, &@block)
|
55
|
-
end
|
56
|
-
|
57
|
-
def test(*args)
|
58
|
-
options = args.extract_options!.merge(
|
59
|
-
raise_on_non_zero_exit: false,
|
60
|
-
verbosity: Logger::DEBUG
|
61
|
-
)
|
62
|
-
_execute(*[*args, options]).success?
|
63
|
-
end
|
64
|
-
|
65
|
-
def execute(*args)
|
66
|
-
_execute(*args).success?
|
67
|
-
end
|
68
|
-
|
69
|
-
def background(*args)
|
70
|
-
warn "[Deprecated] The background method is deprecated. Blame badly behaved pseudo-daemons!"
|
71
|
-
options = args.extract_options!.merge(run_in_background: true)
|
72
|
-
_execute(*[*args, options]).success?
|
73
|
-
end
|
74
|
-
|
75
|
-
def capture(*args)
|
76
|
-
options = { verbosity: Logger::DEBUG }.merge(args.extract_options!)
|
77
|
-
_execute(*[*args, options]).full_stdout.strip
|
78
|
-
end
|
79
|
-
|
80
31
|
def upload!(local, remote, options = {})
|
81
32
|
summarizer = transfer_summarizer('Uploading')
|
82
33
|
with_ssh do |ssh|
|
@@ -110,7 +61,7 @@ module SSHKit
|
|
110
61
|
def transfer_summarizer(action)
|
111
62
|
last_name = nil
|
112
63
|
last_percentage = nil
|
113
|
-
proc do |
|
64
|
+
proc do |_ch, name, transferred, total|
|
114
65
|
percentage = (transferred.to_f * 100 / total.to_f)
|
115
66
|
unless percentage.nan?
|
116
67
|
message = "#{action} #{name} #{percentage.round(2)}%"
|
@@ -129,56 +80,52 @@ module SSHKit
|
|
129
80
|
end
|
130
81
|
end
|
131
82
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
chan.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
#
|
160
|
-
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
chan.on_eof do |ch|
|
169
|
-
# TODO: chan sends EOF before the exit status has been
|
170
|
-
# writtend
|
171
|
-
end
|
83
|
+
def execute_command(cmd)
|
84
|
+
output.log_command_start(cmd)
|
85
|
+
cmd.started = true
|
86
|
+
exit_status = nil
|
87
|
+
with_ssh do |ssh|
|
88
|
+
ssh.open_channel do |chan|
|
89
|
+
chan.request_pty if Netssh.config.pty
|
90
|
+
chan.exec cmd.to_command do |_ch, _success|
|
91
|
+
chan.on_data do |ch, data|
|
92
|
+
cmd.on_stdout(ch, data)
|
93
|
+
output.log_command_data(cmd, :stdout, data)
|
94
|
+
end
|
95
|
+
chan.on_extended_data do |ch, _type, data|
|
96
|
+
cmd.on_stderr(ch, data)
|
97
|
+
output.log_command_data(cmd, :stderr, data)
|
98
|
+
end
|
99
|
+
chan.on_request("exit-status") do |_ch, data|
|
100
|
+
exit_status = data.read_long
|
101
|
+
end
|
102
|
+
#chan.on_request("exit-signal") do |ch, data|
|
103
|
+
# # TODO: This gets called if the program is killed by a signal
|
104
|
+
# # might also be a worthwhile thing to report
|
105
|
+
# exit_signal = data.read_string.to_i
|
106
|
+
# warn ">>> " + exit_signal.inspect
|
107
|
+
# output.log_command_killed(cmd, exit_signal)
|
108
|
+
#end
|
109
|
+
chan.on_open_failed do |_ch|
|
110
|
+
# TODO: What do do here?
|
111
|
+
# I think we should raise something
|
112
|
+
end
|
113
|
+
chan.on_process do |_ch|
|
114
|
+
# TODO: I don't know if this is useful
|
115
|
+
end
|
116
|
+
chan.on_eof do |_ch|
|
117
|
+
# TODO: chan sends EOF before the exit status has been
|
118
|
+
# writtend
|
172
119
|
end
|
173
|
-
chan.wait
|
174
120
|
end
|
175
|
-
|
176
|
-
end
|
177
|
-
# Set exit_status and log the result upon completion
|
178
|
-
if exit_status
|
179
|
-
cmd.exit_status = exit_status
|
180
|
-
output << cmd
|
121
|
+
chan.wait
|
181
122
|
end
|
123
|
+
ssh.loop
|
124
|
+
end
|
125
|
+
# Set exit_status and log the result upon completion
|
126
|
+
if exit_status
|
127
|
+
cmd.exit_status = exit_status
|
128
|
+
output.log_command_exit(cmd)
|
182
129
|
end
|
183
130
|
end
|
184
131
|
|
@@ -1,35 +1,15 @@
|
|
1
1
|
module SSHKit
|
2
2
|
module Backend
|
3
3
|
|
4
|
+
# Printer is used to implement --dry-run in Capistrano
|
4
5
|
class Printer < Abstract
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
def run
|
9
|
-
instance_exec(host, &@block)
|
7
|
+
def execute_command(cmd)
|
8
|
+
output.log_command_start(cmd)
|
10
9
|
end
|
11
10
|
|
12
|
-
def execute(*args)
|
13
|
-
command(*args).tap do |cmd|
|
14
|
-
output << cmd
|
15
|
-
end
|
16
|
-
end
|
17
11
|
alias :upload! :execute
|
18
12
|
alias :download! :execute
|
19
|
-
alias :test :execute
|
20
|
-
|
21
|
-
def capture(*args)
|
22
|
-
String.new.tap { execute(*args) }
|
23
|
-
end
|
24
|
-
alias :capture! :capture
|
25
|
-
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def output
|
30
|
-
SSHKit.config.output
|
31
|
-
end
|
32
|
-
|
33
13
|
end
|
34
14
|
end
|
35
15
|
end
|
@@ -1,30 +1,26 @@
|
|
1
1
|
module SSHKit
|
2
2
|
module Backend
|
3
3
|
|
4
|
-
class Skipper <
|
4
|
+
class Skipper < Abstract
|
5
5
|
|
6
6
|
def initialize(&block)
|
7
7
|
@block = block
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
12
|
-
warn "[SKIPPING] No Matching Host for #{cmd}"
|
13
|
-
end
|
10
|
+
def execute_command(cmd)
|
11
|
+
warn "[SKIPPING] No Matching Host for #{cmd}"
|
14
12
|
end
|
15
13
|
alias :upload! :execute
|
16
14
|
alias :download! :execute
|
17
15
|
alias :test :execute
|
18
|
-
alias :invoke :execute
|
19
16
|
|
20
|
-
def info(
|
17
|
+
def info(_messages)
|
21
18
|
# suppress all messages except `warn`
|
22
19
|
end
|
23
20
|
alias :log :info
|
24
21
|
alias :fatal :info
|
25
22
|
alias :error :info
|
26
23
|
alias :debug :info
|
27
|
-
alias :trace :info
|
28
24
|
|
29
25
|
end
|
30
26
|
end
|