-
Notifications
You must be signed in to change notification settings - Fork 253
/
netssh.rb
203 lines (176 loc) · 5.34 KB
/
netssh.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
require 'net/ssh'
require 'net/scp'
module Net
module SSH
class Config
class << self
def default_files
@@default_files + [File.join(Dir.pwd, '.ssh/config')]
end
end
end
end
end
module SSHKit
class Logger
class Net::SSH::LogLevelShim
attr_reader :output
def initialize(output)
@output = output
end
def debug(args)
output << LogMessage.new(Logger::TRACE, args)
end
def error(args)
output << LogMessage.new(Logger::ERROR, args)
end
def lwarn(args)
output << LogMessage.new(Logger::WARN, args)
end
end
end
module Backend
class Netssh < Printer
class Configuration
attr_accessor :connection_timeout, :pty
attr_writer :ssh_options
def ssh_options
@ssh_options || {}
end
end
include SSHKit::CommandHelper
def run
instance_exec(host, &@block)
end
def test(*args)
options = args.extract_options!.merge(
raise_on_non_zero_exit: false,
verbosity: Logger::DEBUG
)
_execute(*[*args, options]).success?
end
def execute(*args)
_execute(*args).success?
end
def background(*args)
warn "[Deprecated] The background method is deprecated. Blame badly behaved pseudo-daemons!"
options = args.extract_options!.merge(run_in_background: true)
_execute(*[*args, options]).success?
end
def capture(*args)
options = { verbosity: Logger::DEBUG }.merge(args.extract_options!)
_execute(*[*args, options]).full_stdout.strip
end
def upload!(local, remote, options = {})
summarizer = transfer_summarizer('Uploading')
with_ssh do |ssh|
ssh.scp.upload!(local, remote, options, &summarizer)
end
end
def download!(remote, local=nil, options = {})
summarizer = transfer_summarizer('Downloading')
with_ssh do |ssh|
ssh.scp.download!(remote, local, options, &summarizer)
end
end
@pool = SSHKit::Backend::ConnectionPool.new
class << self
attr_accessor :pool
def configure
yield config
end
def config
@config ||= Configuration.new
end
end
private
def transfer_summarizer(action)
last_name = nil
last_percentage = nil
proc do |ch, name, transferred, total|
percentage = (transferred.to_f * 100 / total.to_f)
unless percentage.nan?
message = "#{action} #{name} #{percentage.round(2)}%"
percentage_r = (percentage / 10).truncate * 10
if percentage_r > 0 && (last_name != name || last_percentage != percentage_r)
info message
last_name = name
last_percentage = percentage_r
else
debug message
end
else
warn "Error calculating percentage #{transferred}/#{total}, " <<
"is #{name} empty?"
end
end
end
def _execute(*args)
command(*args).tap do |cmd|
output << cmd
cmd.started = true
exit_status = nil
with_ssh do |ssh|
ssh.open_channel do |chan|
chan.request_pty if Netssh.config.pty
chan.exec cmd.to_command do |ch, success|
chan.on_data do |ch, data|
cmd.stdout = data
cmd.full_stdout += data
output << cmd
end
chan.on_extended_data do |ch, type, data|
cmd.stderr = data
cmd.full_stderr += data
output << cmd
end
chan.on_request("exit-status") do |ch, data|
exit_status = data.read_long
end
#chan.on_request("exit-signal") do |ch, data|
# # TODO: This gets called if the program is killed by a signal
# # might also be a worthwhile thing to report
# exit_signal = data.read_string.to_i
# warn ">>> " + exit_signal.inspect
# output << cmd
#end
chan.on_open_failed do |ch|
# TODO: What do do here?
# I think we should raise something
end
chan.on_process do |ch|
# TODO: I don't know if this is useful
end
chan.on_eof do |ch|
# TODO: chan sends EOF before the exit status has been
# writtend
end
end
chan.wait
end
ssh.loop
end
# Set exit_status and log the result upon completion
if exit_status
cmd.exit_status = exit_status
output << cmd
end
end
end
def with_ssh
host.ssh_options = Netssh.config.ssh_options.merge(host.ssh_options || {})
conn = self.class.pool.checkout(
String(host.hostname),
host.username,
host.netssh_options,
&Net::SSH.method(:start)
)
begin
yield conn.connection
ensure
self.class.pool.checkin conn
end
end
end
end
end