net-ssh-cli 0.2.0 → 0.3.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/README.md +49 -5
- data/bin/localhost +17 -23
- data/lib/net/ssh/cli/version.rb +1 -1
- data/lib/net/ssh/cli.rb +137 -180
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36080ea8a1e4e289bc73d79d9a665a0848925799db74242e5705814a8c6081c9
|
4
|
+
data.tar.gz: 85cbb73ba6ff4925b39955c7416934a5c9124b8e4e5cbf0eeafc67af1bb2193f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e3a0db15b0bb3d14e9cc2f7a1bd8c5e3379c8e2829e09fc8e3f63a445514f6b5a467cc3de76be5da34ec33632e8545bf8eea10ea2898eade24496f6fca222cb
|
7
|
+
data.tar.gz: 9b15edce5dc418753069b56004480bd9836104bfd9508b3e2dc89c31acdf3433253b76717bde7959dc6e4dcc53fcbbdedea1f8dee79f95359f2615feb37f402f
|
data/README.md
CHANGED
@@ -29,7 +29,7 @@ Or install it yourself as:
|
|
29
29
|
|
30
30
|
```ruby
|
31
31
|
Net::SSH.start('host', 'user', password: "password") do |ssh|
|
32
|
-
cli = ssh.
|
32
|
+
cli = ssh.cli(default_prompt: /(\nuser@host):/m)
|
33
33
|
cli.cmd ""
|
34
34
|
# => "Last login: \nuser@host:"
|
35
35
|
|
@@ -40,18 +40,18 @@ end
|
|
40
40
|
|
41
41
|
```ruby
|
42
42
|
net_ssh = Net::SSH.start('host', 'user', password: "password")
|
43
|
-
cli = Net::SSH::CLI::
|
43
|
+
cli = Net::SSH::CLI::Session.new(net_ssh: net_ssh)
|
44
44
|
cli.cmd ""
|
45
45
|
```
|
46
46
|
|
47
47
|
```ruby
|
48
|
-
cli = Net::SSH::CLI::
|
48
|
+
cli = Net::SSH::CLI::Session.new(net_ssh_options: {host: 'host', user: 'user', password: 'password'})
|
49
49
|
cli.cmd ""
|
50
50
|
```
|
51
51
|
|
52
52
|
### #cmd
|
53
53
|
```ruby
|
54
|
-
cli = ssh.
|
54
|
+
cli = ssh.cli(default_prompt: /(\nuser@host):/m)
|
55
55
|
cli.cmd "echo 'bananas'"
|
56
56
|
# => "echo 'bananas'\nbananas\nuser@host:"
|
57
57
|
cli.cmd "echo 'bananas'", rm_command: true
|
@@ -64,7 +64,7 @@ end
|
|
64
64
|
|
65
65
|
Remove the command and the prompt for #cmd & #dialog by default
|
66
66
|
```ruby
|
67
|
-
cli = ssh.
|
67
|
+
cli = ssh.cli(default_prompt: /(\nuser@host):/m, cmd_rm_command: true, cmd_rm_prompt: true)
|
68
68
|
cli.cmd "echo 'bananas'"
|
69
69
|
# => "bananas"
|
70
70
|
```
|
@@ -76,6 +76,50 @@ Remove the command and the prompt for #cmd & #dialog by default
|
|
76
76
|
cli.cmd "yes"
|
77
77
|
```
|
78
78
|
|
79
|
+
### #read & #write
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
cli.write "echo 'hello'\n"
|
83
|
+
# => "echo 'hello'\n"
|
84
|
+
cli.read
|
85
|
+
# => "echo 'hello'\nhello\nuser@host:"
|
86
|
+
```
|
87
|
+
|
88
|
+
### #write_n
|
89
|
+
```ruby
|
90
|
+
cli.write_n "echo 'hello'"
|
91
|
+
# => "echo 'hello'\n"
|
92
|
+
```
|
93
|
+
|
94
|
+
### #read_till
|
95
|
+
keep on processing till the stdout matches to given|default prompt and then read the whole stdin.
|
96
|
+
```ruby
|
97
|
+
cli.write "\n"
|
98
|
+
# => "echo 'hello'\n"
|
99
|
+
cli.read_till
|
100
|
+
# => "echo 'hello'\nhello\nuser@host:"
|
101
|
+
```
|
102
|
+
|
103
|
+
This method is used by #cmd
|
104
|
+
|
105
|
+
## Configuration
|
106
|
+
|
107
|
+
Have a deep look at ``Net::SSH::CLI::OPTIONS`` at ``lib/net/ssh/cli.rb``
|
108
|
+
|
109
|
+
### Callbacks
|
110
|
+
|
111
|
+
The following callbacks are available
|
112
|
+
- #before_open_channel
|
113
|
+
- #after_open_channel
|
114
|
+
- #before_on_data
|
115
|
+
- #before_on_data
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
cli.before_open_channel do
|
119
|
+
puts "The channel will open soon"
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
79
123
|
## Development
|
80
124
|
|
81
125
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/bin/localhost
CHANGED
@@ -3,35 +3,29 @@
|
|
3
3
|
|
4
4
|
require 'bundler/setup'
|
5
5
|
require 'net/ssh/cli'
|
6
|
-
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
9
|
-
|
10
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
-
# require "pry"
|
12
|
-
# Pry.start
|
13
|
-
|
14
6
|
require 'irb'
|
15
7
|
|
16
|
-
def reload!
|
17
|
-
load __FILE__
|
18
|
-
end
|
19
|
-
|
20
|
-
$CLI = nil
|
21
8
|
begin
|
22
|
-
$
|
9
|
+
$NET_SSH = Net::SSH.start("localhost")
|
10
|
+
$CLI = Net::SSH::CLI::Session.new(net_ssh: $NET_SSH, default_prompt: "@")
|
23
11
|
$CLI.open_channel
|
24
|
-
puts "
|
25
|
-
|
26
|
-
puts "\nPUTS #{$CLI.read}"
|
27
|
-
puts "\nPUTS #{$CLI.write "\n"}"
|
28
|
-
puts "\nPUTS TILL #{$CLI.read_till}"
|
29
|
-
puts "try $CLI.cmd"
|
30
|
-
puts File.read(__FILE__)
|
12
|
+
puts "assuming your prompt contains '@'"
|
13
|
+
puts $CLI.cmd "echo 'hello world'"
|
31
14
|
rescue StandardError => error
|
32
|
-
puts error.class
|
33
|
-
puts error.message
|
15
|
+
puts "#{error.class} #{error.message}"
|
34
16
|
puts error.backtrace
|
35
17
|
ensure
|
18
|
+
puts ""
|
19
|
+
puts File.read(__FILE__).lines.map {|line| "[bin/localhost] " + line}
|
20
|
+
puts ""
|
36
21
|
IRB.start(__FILE__)
|
37
22
|
end
|
23
|
+
|
24
|
+
## Try one of those
|
25
|
+
# $CLI.cmd "echo 'hello world'"
|
26
|
+
# $CLI.detect_prompt
|
27
|
+
# $CLI.default_prompt
|
28
|
+
# $CLI.cmd "cat /etc/passwd"
|
29
|
+
# $CLI.write "cat /etc/passwd"
|
30
|
+
# $CLI.read
|
31
|
+
# $CLI.cmd "echo 'hello world'"
|
data/lib/net/ssh/cli/version.rb
CHANGED
data/lib/net/ssh/cli.rb
CHANGED
@@ -16,42 +16,50 @@ module Net
|
|
16
16
|
class UndefinedMatch < Error; end
|
17
17
|
class OpenChannelTimeout < Error; end
|
18
18
|
class ReadTillTimeout < Error; end
|
19
|
+
class PromptDetection < Error; end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Example
|
23
|
+
# net_ssh = Net::SSH.start("localhost")
|
24
|
+
# net_ssh_cli = Net::SSH::CLI.start(net_ssh: net_ssh)
|
25
|
+
# net_ssh_cli.cmd "cat /etc/passwd"
|
26
|
+
# => "root:x:0:0:root:/root:/bin/bash\n..."
|
27
|
+
def self.start(**opts)
|
28
|
+
Net::SSH::CLI::Session.new(**opts)
|
19
29
|
end
|
20
30
|
|
21
31
|
def initialize(**opts)
|
22
32
|
options.merge!(opts)
|
23
33
|
self.net_ssh = options.delete(:net_ssh)
|
24
34
|
self.logger = options.delete(:logger) || Logger.new(STDOUT, level: Logger::WARN)
|
25
|
-
open_channel unless lazy
|
26
|
-
@with_prompt = []
|
27
35
|
end
|
28
36
|
|
29
|
-
attr_accessor :channel, :stdout, :
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
attr_accessor :channel, :stdout, :net_ssh, :logger
|
38
|
+
|
39
|
+
OPTIONS = ActiveSupport::HashWithIndifferentAccess.new(
|
40
|
+
default_prompt: /\n?^(\S+@.*)\z/, # the default prompt to search for
|
41
|
+
cmd_rm_prompt: false, # whether the prompt should be removed in the output of #cmd
|
42
|
+
cmd_rm_command: false, # whether the given command should be removed in the output of #cmd
|
43
|
+
read_till_timeout: nil, # timeout for #read_till to find the match
|
44
|
+
named_prompts: ActiveSupport::HashWithIndifferentAccess.new, # you can used named prompts for #with_prompt {}
|
45
|
+
before_on_stdout_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before data arrives from the underlying connection
|
46
|
+
after_on_stdout_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after data arrives from the underlying connection
|
47
|
+
before_open_channel_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before opening a channel
|
48
|
+
after_open_channel_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after opening a channel, for example you could call #detect_prompt or #read_till
|
49
|
+
open_channel_timeout: nil, # timeout to open the channel
|
50
|
+
net_ssh_options: ActiveSupport::HashWithIndifferentAccess.new, # a wrapper for options to pass to Net::SSH.start in case net_ssh is undefined
|
51
|
+
process_time: 0.00001, # how long #process is processing net_ssh#process or sleeping (waiting for something)
|
52
|
+
background_processing: false, # default false, whether the process method maps to the underlying net_ssh#process or the net_ssh#process happens in a separate loop
|
42
53
|
)
|
43
54
|
|
44
|
-
def default
|
45
|
-
@default ||= DEFAULT.clone
|
46
|
-
end
|
47
|
-
|
48
|
-
def default!(**defaults)
|
49
|
-
default.merge!(**defaults)
|
50
|
-
end
|
51
|
-
|
52
|
-
# don't even think about nesting hashes here
|
53
55
|
def options
|
54
|
-
@options ||=
|
56
|
+
@options ||= begin
|
57
|
+
opts = OPTIONS.clone
|
58
|
+
opts.each do |key,value|
|
59
|
+
opts[key] = value.clone if value.is_a?(Hash)
|
60
|
+
end
|
61
|
+
opts
|
62
|
+
end
|
55
63
|
end
|
56
64
|
|
57
65
|
# don't even think about nesting hashes here
|
@@ -59,63 +67,28 @@ module Net
|
|
59
67
|
options.merge!(opts)
|
60
68
|
end
|
61
69
|
|
62
|
-
# don't even think about nesting hashes here
|
63
70
|
def options=(opts)
|
64
71
|
@options = ActiveSupport::HashWithIndifferentAccess.new(opts)
|
65
72
|
end
|
66
73
|
|
67
|
-
|
74
|
+
OPTIONS.keys.each do |name|
|
68
75
|
define_method name do
|
69
76
|
options[name]
|
70
77
|
end
|
71
78
|
define_method "#{name}=" do |value|
|
72
79
|
options[name] = value
|
73
80
|
end
|
74
|
-
|
75
|
-
|
76
|
-
%i[process_stdout_procs process_stderr_procs named_prompts net_ssh_options open_channel_options].each do |name|
|
77
|
-
define_method name do
|
78
|
-
options[name] ||= ActiveSupport::HashWithIndifferentAccess.new
|
79
|
-
end
|
80
|
-
define_method "#{name}!" do |**opts|
|
81
|
-
send(name).merge!(**opts)
|
82
|
-
end
|
83
|
-
define_method "#{name}=" do |value|
|
84
|
-
options[name] = ActiveSupport::HashWithIndifferentAccess.new(value)
|
81
|
+
define_method "#{name}?" do
|
82
|
+
!!options[name]
|
85
83
|
end
|
86
84
|
end
|
87
85
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
## Net::SSH instance
|
93
|
-
#
|
94
|
-
|
95
|
-
def net_ssh
|
96
|
-
return @net_ssh if @net_ssh
|
97
|
-
|
98
|
-
logger.debug { 'Net:SSH #start' }
|
99
|
-
self.net_ssh = Net::SSH.start(net_ssh_options[:ip] || net_ssh_options[:host], net_ssh_options[:user] || ENV['USER'], formatted_net_ssh_options)
|
100
|
-
rescue StandardError => error
|
101
|
-
self.net_ssh = nil
|
102
|
-
raise
|
103
|
-
end
|
104
|
-
alias proxy net_ssh
|
105
|
-
|
106
|
-
def host
|
107
|
-
net_ssh_options[:host] || net_ssh_options[:hostname] || net_ssh_options[:ip] || @net_ssh&.host
|
108
|
-
end
|
109
|
-
alias to_s host
|
110
|
-
alias hostname host
|
111
|
-
|
112
|
-
def ip
|
113
|
-
net_ssh_options[:ip]
|
86
|
+
OPTIONS.keys.select {|key| key.to_s.include? "procs"}.each do |name|
|
87
|
+
define_method name.sub("_procs","") do |&blk|
|
88
|
+
self.send(name)[SecureRandom.uuid] = Proc.new {blk.call}
|
89
|
+
end
|
114
90
|
end
|
115
91
|
|
116
|
-
## channel & stderr|stdout stream handling
|
117
|
-
#
|
118
|
-
|
119
92
|
def stdout
|
120
93
|
@stdout ||= String.new
|
121
94
|
end
|
@@ -126,65 +99,15 @@ module Net
|
|
126
99
|
var
|
127
100
|
end
|
128
101
|
|
129
|
-
def
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
def stderr!
|
134
|
-
var = stderr
|
135
|
-
self.stderr = String.new
|
136
|
-
var
|
137
|
-
end
|
138
|
-
|
139
|
-
def open_channel # cli_channel
|
140
|
-
::Timeout.timeout(open_channel_options[:timeout], Error::OpenChannelTimeout) do
|
141
|
-
net_ssh.open_channel do |channel_|
|
142
|
-
logger.debug 'channel is open'
|
143
|
-
self.channel = channel_
|
144
|
-
channel_.request_pty do |_ch, success|
|
145
|
-
raise Error::Pty, "#{host || ip} Failed to open ssh pty" unless success
|
146
|
-
end
|
147
|
-
channel_.send_channel_request('shell') do |_ch, success|
|
148
|
-
raise Error::RequestShell, 'Failed to open ssh shell' unless success
|
149
|
-
end
|
150
|
-
channel_.on_data do |_ch, data|
|
151
|
-
process_stdout(data)
|
152
|
-
end
|
153
|
-
channel_.on_extended_data do |_ch, type, data|
|
154
|
-
process_stderr(data, type)
|
155
|
-
end
|
156
|
-
channel_.on_close do
|
157
|
-
close
|
158
|
-
end
|
159
|
-
end
|
160
|
-
until channel do process end
|
161
|
-
end
|
162
|
-
logger.debug 'channel is ready, running callbacks now'
|
163
|
-
read_till if open_channel_options[:after_read_till_prompt]
|
164
|
-
open_channel_options[:after_proc]&.call
|
165
|
-
process
|
166
|
-
rescue StandardError => error
|
167
|
-
close
|
168
|
-
raise
|
169
|
-
end
|
170
|
-
|
171
|
-
def process_stdout(data)
|
102
|
+
def on_stdout(data)
|
103
|
+
before_on_stdout_procs.each { |_name, a_proc| a_proc.call }
|
172
104
|
stdout << data
|
105
|
+
after_on_stdout_procs.each { |_name, a_proc| a_proc.call }
|
173
106
|
process # if we receive data, we probably receive more - improves performance
|
174
|
-
process_stdout_procs.each { |_name, a_proc| a_proc.call }
|
175
107
|
stdout
|
176
108
|
end
|
177
109
|
|
178
|
-
def process_stderr(data, _type)
|
179
|
-
stderr << data
|
180
|
-
process # if we receive data, we probably receive more - improves performance
|
181
|
-
process_stderr_procs.each { |_name, a_proc| a_proc.call }
|
182
|
-
stderr
|
183
|
-
end
|
184
|
-
|
185
110
|
def write(content = String.new)
|
186
|
-
raise Error, 'channel is not stablished or gone' unless channel
|
187
|
-
|
188
111
|
logger.debug { "#write #{content.inspect}" }
|
189
112
|
channel.send_data content
|
190
113
|
process
|
@@ -196,7 +119,6 @@ module Net
|
|
196
119
|
write content + "\n"
|
197
120
|
end
|
198
121
|
|
199
|
-
# returns the stdout buffer and empties it
|
200
122
|
def read
|
201
123
|
process
|
202
124
|
var = stdout!
|
@@ -208,7 +130,7 @@ module Net
|
|
208
130
|
#
|
209
131
|
|
210
132
|
def current_prompt
|
211
|
-
|
133
|
+
with_prompts[-1] || default_prompt
|
212
134
|
end
|
213
135
|
|
214
136
|
def with_named_prompt(name)
|
@@ -219,19 +141,26 @@ module Net
|
|
219
141
|
end
|
220
142
|
end
|
221
143
|
|
222
|
-
def detect_prompt(seconds:
|
223
|
-
|
224
|
-
|
144
|
+
def detect_prompt(seconds: 3)
|
145
|
+
write_n
|
146
|
+
future = Time.now + seconds
|
147
|
+
while future > Time.now
|
148
|
+
process
|
149
|
+
sleep 0.1
|
150
|
+
end
|
151
|
+
self.default_prompt = read[/\n?^.*\z/]
|
152
|
+
raise Error::PromptDetection, "couldn't detect a prompt" unless default_prompt.present?
|
153
|
+
default_prompt
|
225
154
|
end
|
226
155
|
|
227
156
|
# prove a block where the default prompt changes
|
228
157
|
def with_prompt(prompt)
|
229
158
|
logger.debug { "#with_prompt: #{current_prompt.inspect} => #{prompt.inspect}" }
|
230
|
-
|
159
|
+
with_prompts << prompt
|
231
160
|
yield
|
232
161
|
prompt
|
233
162
|
ensure
|
234
|
-
|
163
|
+
with_prompts.delete_at(-1)
|
235
164
|
logger.debug { "#with_prompt: => #{current_prompt.inspect}" }
|
236
165
|
end
|
237
166
|
|
@@ -240,59 +169,51 @@ module Net
|
|
240
169
|
|
241
170
|
::Timeout.timeout(timeout, Error::ReadTillTimeout.new("output did not prompt #{prompt.inspect} within #{timeout}")) do
|
242
171
|
with_prompt(prompt) do
|
243
|
-
|
172
|
+
until stdout[current_prompt] do
|
173
|
+
process
|
174
|
+
sleep 0.1
|
175
|
+
end
|
244
176
|
end
|
245
177
|
end
|
246
178
|
read
|
247
179
|
end
|
248
180
|
|
249
181
|
def read_for(seconds:)
|
250
|
-
process
|
251
|
-
sleep seconds
|
252
|
-
process
|
182
|
+
process(seconds)
|
253
183
|
read
|
254
184
|
end
|
255
185
|
|
256
186
|
def dialog(command, prompt, **opts)
|
257
|
-
|
258
|
-
|
259
|
-
write command
|
260
|
-
output = read_till(prompt: prompt, **opts)
|
261
|
-
rm_prompt!(output, prompt: prompt, **opts)
|
262
|
-
rm_command!(output, command, prompt: prompt, **opts)
|
263
|
-
output
|
187
|
+
opts = opts.clone.merge(prompt: prompt)
|
188
|
+
cmd(command, opts)
|
264
189
|
end
|
265
190
|
|
266
191
|
# 'read' first on purpuse as a feature. once you cmd you ignore what happend before. otherwise use read|write directly.
|
267
192
|
# this should avoid many horrible state issues where the prompt is not the last prompt
|
268
|
-
def cmd(command, **opts)
|
269
|
-
|
270
|
-
|
193
|
+
def cmd(command, pre_read: true, rm_prompt: cmd_rm_prompt, rm_command: cmd_rm_command, prompt: current_prompt, **opts)
|
194
|
+
opts = opts.clone.merge(pre_read: pre_read, rm_prompt: rm_prompt, rm_command: rm_command, prompt: prompt)
|
195
|
+
if pre_read
|
196
|
+
pre_read_data = read
|
197
|
+
logger.debug { "#cmd ignoring pre-command output: #{pre_read_data.inspect}" } if pre_read_data.present?
|
198
|
+
end
|
271
199
|
write_n command
|
272
|
-
output = read_till(
|
273
|
-
rm_prompt!(output,
|
274
|
-
rm_command!(output, command,
|
200
|
+
output = read_till(opts)
|
201
|
+
rm_prompt!(output, opts)
|
202
|
+
rm_command!(output, command, opts)
|
275
203
|
output
|
276
204
|
end
|
277
205
|
alias command cmd
|
206
|
+
alias exec cmd
|
278
207
|
|
279
208
|
def cmds(commands, **opts)
|
280
209
|
commands.map { |command| [command, cmd(command, **opts)] }
|
281
210
|
end
|
282
211
|
alias commands cmds
|
283
212
|
|
284
|
-
def rm_command?(**opts)
|
285
|
-
opts[:rm_cmd].nil? ? cmd_rm_command : opts[:rm_cmd]
|
286
|
-
end
|
287
|
-
|
288
213
|
def rm_command!(output, command, **opts)
|
289
214
|
output[command + "\n"] = '' if rm_command?(opts) && output[command + "\n"]
|
290
215
|
end
|
291
216
|
|
292
|
-
def rm_prompt?(**opts)
|
293
|
-
opts[:rm_prompt].nil? ? cmd_rm_prompt : opts[:rm_prompt]
|
294
|
-
end
|
295
|
-
|
296
217
|
def rm_prompt!(output, **opts)
|
297
218
|
if rm_prompt?(opts)
|
298
219
|
prompt = opts[:prompt] || current_prompt
|
@@ -302,61 +223,97 @@ module Net
|
|
302
223
|
end
|
303
224
|
end
|
304
225
|
|
226
|
+
def host
|
227
|
+
@net_ssh&.host
|
228
|
+
end
|
229
|
+
alias hostname host
|
230
|
+
alias to_s host
|
231
|
+
|
305
232
|
## NET::SSH
|
306
233
|
#
|
307
234
|
|
235
|
+
def net_ssh
|
236
|
+
return @net_ssh if @net_ssh
|
237
|
+
|
238
|
+
logger.debug { 'Net:SSH #start' }
|
239
|
+
self.net_ssh = Net::SSH.start(net_ssh_options[:ip] || net_ssh_options[:host] || "localhost", net_ssh_options[:user] || ENV['USER'], formatted_net_ssh_options)
|
240
|
+
rescue StandardError => error
|
241
|
+
self.net_ssh = nil
|
242
|
+
raise
|
243
|
+
end
|
244
|
+
alias proxy net_ssh
|
245
|
+
|
246
|
+
# have a deep look at the source of Net::SSH
|
247
|
+
# session#process https://github.com/net-ssh/net-ssh/blob/dd13dd44d68b7fa82d4ca9a3bbe18e30c855f1d2/lib/net/ssh/connection/session.rb#L227
|
248
|
+
# session#loop https://github.com/net-ssh/net-ssh/blob/dd13dd44d68b7fa82d4ca9a3bbe18e30c855f1d2/lib/net/ssh/connection/session.rb#L179
|
249
|
+
# because the (cli) channel stays open, we always need to ensure that the ssh layer gets "processed" further. This can be done inside here automatically or outside in a separate event loop for the net_ssh connection.
|
308
250
|
def process(time = process_time)
|
309
|
-
net_ssh.process(time)
|
251
|
+
background_processing? ? sleep(time) : net_ssh.process(time)
|
310
252
|
rescue IOError => error
|
311
253
|
raise Error, error.message
|
312
254
|
end
|
313
255
|
|
314
|
-
def
|
315
|
-
|
256
|
+
def open_channel # cli_channel
|
257
|
+
before_open_channel_procs.each { |_name, a_proc| a_proc.call }
|
258
|
+
::Timeout.timeout(open_channel_timeout, Error::OpenChannelTimeout) do
|
259
|
+
net_ssh.open_channel do |new_channel|
|
260
|
+
logger.debug 'channel is open'
|
261
|
+
self.channel = new_channel
|
262
|
+
new_channel.request_pty do |_ch, success|
|
263
|
+
raise Error::Pty, "#{host || ip} Failed to open ssh pty" unless success
|
264
|
+
end
|
265
|
+
new_channel.send_channel_request('shell') do |_ch, success|
|
266
|
+
raise Error::RequestShell, 'Failed to open ssh shell' unless success
|
267
|
+
end
|
268
|
+
new_channel.on_data do |_ch, data|
|
269
|
+
on_stdout(data)
|
270
|
+
end
|
271
|
+
#new_channel.on_extended_data do |_ch, type, data| end
|
272
|
+
#new_channel.on_close do end
|
273
|
+
end
|
274
|
+
until channel do process end
|
275
|
+
end
|
276
|
+
logger.debug 'channel is ready, running callbacks now'
|
277
|
+
after_open_channel_procs.each { |_name, a_proc| a_proc.call }
|
278
|
+
process
|
279
|
+
self
|
280
|
+
end
|
316
281
|
|
317
|
-
|
282
|
+
def close_channel
|
283
|
+
net_ssh&.cleanup_channel(channel) if channel
|
318
284
|
self.channel = nil
|
319
|
-
# ssh.close if ssh.channels.none? # should the connection be closed if the last channel gets closed?
|
320
285
|
end
|
321
286
|
|
322
|
-
|
323
|
-
open_channel unless channel
|
324
|
-
end
|
287
|
+
private
|
325
288
|
|
326
|
-
def
|
327
|
-
|
328
|
-
connect
|
289
|
+
def with_prompts
|
290
|
+
@with_prompts ||= []
|
329
291
|
end
|
330
292
|
|
331
|
-
|
332
|
-
|
333
|
-
def disconnect
|
334
|
-
close
|
335
|
-
net_ssh&.close
|
336
|
-
self.net_ssh = nil
|
293
|
+
def formatted_net_ssh_options
|
294
|
+
net_ssh_options.symbolize_keys.reject {|k,v| [:host, :ip, :user].include?(k)}
|
337
295
|
end
|
338
296
|
|
339
|
-
def
|
340
|
-
|
297
|
+
def rm_prompt?(**opts)
|
298
|
+
opts[:rm_prompt].nil? ? cmd_rm_prompt : opts[:rm_prompt]
|
341
299
|
end
|
342
300
|
|
343
|
-
|
344
|
-
|
301
|
+
def rm_command?(**opts)
|
302
|
+
opts[:rm_cmd].nil? ? cmd_rm_command : opts[:rm_cmd]
|
303
|
+
end
|
345
304
|
end
|
346
305
|
end
|
347
306
|
end
|
348
307
|
|
349
|
-
class Net::SSH::CLI::
|
308
|
+
class Net::SSH::CLI::Session
|
350
309
|
include Net::SSH::CLI
|
351
|
-
def initialize(**options)
|
352
|
-
super
|
353
|
-
# open_channel
|
354
|
-
end
|
355
310
|
end
|
356
311
|
|
357
312
|
class Net::SSH::Connection::Session
|
358
313
|
attr_accessor :cli_channels
|
359
|
-
def
|
360
|
-
Net::SSH::CLI::
|
314
|
+
def cli(**opts)
|
315
|
+
cli_session = Net::SSH::CLI::Session.new({net_ssh: self}.merge(opts))
|
316
|
+
cli_session.open_channel
|
317
|
+
cli_session
|
361
318
|
end
|
362
319
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-ssh-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fabian Stillhart
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|