net-ssh-cli 1.9.1 → 1.9.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 +4 -4
- data/lib/net/ssh/cli/version.rb +1 -1
- data/lib/net/ssh/cli.rb +70 -73
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e1db663e9e79a41c9c5fbb7b0dce5918a0bd61b83fb98f8d9314f1a4602a698
|
4
|
+
data.tar.gz: d7841ea18aefa288771a8ef64459373824b2c307b5f4b8e89a0b636fdfceb172
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e33f5fd908ea9c927913692add3a4a112359e85875eb64af988c4f8d9b6d0fc6e3fa877583ec5be8ebb2f6dd62c83c2c5eebd8df858d07897e08c62bfa0452e
|
7
|
+
data.tar.gz: 92007ac57dd811683cc6715592c2544ab28e7a0be42237e65f8d3d71ed44e2c3d2df815d1bb6395b1f73294e00529b6a5f8a11cd257396b6a2623b34dcab7126
|
data/lib/net/ssh/cli/version.rb
CHANGED
data/lib/net/ssh/cli.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
3
|
+
require "net/ssh/cli/version"
|
4
|
+
require "net/ssh"
|
5
|
+
require "active_support/core_ext/hash/indifferent_access"
|
6
|
+
require "active_support/core_ext/object/blank"
|
7
|
+
require "timeout"
|
8
|
+
require "logger"
|
9
9
|
|
10
10
|
module Net
|
11
11
|
module SSH
|
@@ -40,36 +40,36 @@ module Net
|
|
40
40
|
attr_accessor :channel, :stdout, :net_ssh, :logger, :new_data, :process_count
|
41
41
|
|
42
42
|
OPTIONS = ActiveSupport::HashWithIndifferentAccess.new(
|
43
|
-
default_prompt:
|
44
|
-
cmd_rm_prompt:
|
45
|
-
cmd_rm_command:
|
46
|
-
cmd_rm_command_tail:
|
47
|
-
cmd_minimum_duration:
|
48
|
-
run_impact:
|
49
|
-
read_till_timeout:
|
50
|
-
read_till_hard_timeout:
|
51
|
-
read_till_hard_timeout_factor: 1.2,
|
52
|
-
named_prompts:
|
53
|
-
before_cmd_procs:
|
54
|
-
after_cmd_procs:
|
55
|
-
before_on_stdout_procs:
|
56
|
-
after_on_stdout_procs:
|
57
|
-
before_on_stdin_procs:
|
58
|
-
after_on_stdin_procs:
|
43
|
+
default_prompt: /\n?^(\S+@.*)\z/, # the default prompt to search for. It is recommended to use \z to ensure you don't match the prompt too early.
|
44
|
+
cmd_rm_prompt: false, # whether the prompt should be removed in the output of #cmd
|
45
|
+
cmd_rm_command: false, # whether the given command should be removed in the output of #cmd
|
46
|
+
cmd_rm_command_tail: "\n", # which format does the end of line return after a command has been submitted. Could be something like "ls\n" "ls\r\n" or "ls \n" (extra spaces)
|
47
|
+
cmd_minimum_duration: 0, # how long do you want to wait/sleep after sending the command. After this waiting time, the output will be processed and the prompt will be searched.
|
48
|
+
run_impact: false, # whether to run #impact commands. This might align with testing|development|production. example #impact("reboot")
|
49
|
+
read_till_timeout: nil, # timeout for #read_till to find the match
|
50
|
+
read_till_hard_timeout: nil, # hard timeout for #read_till to find the match using Timeout.timeout(hard_timeout) {}. Might creates unpredicted sideffects
|
51
|
+
read_till_hard_timeout_factor: 1.2, # hard timeout factor in case read_till_hard_timeout is true
|
52
|
+
named_prompts: ActiveSupport::HashWithIndifferentAccess.new, # you can used named prompts for #with_prompt {}
|
53
|
+
before_cmd_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before #cmd
|
54
|
+
after_cmd_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after #cmd
|
55
|
+
before_on_stdout_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before data arrives from the underlying connection
|
56
|
+
after_on_stdout_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after data arrives from the underlying connection
|
57
|
+
before_on_stdin_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before data is sent to the underlying channel
|
58
|
+
after_on_stdin_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after data is sent to the underlying channel
|
59
59
|
before_open_channel_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before opening a channel
|
60
|
-
after_open_channel_procs:
|
61
|
-
open_channel_timeout:
|
62
|
-
net_ssh_options:
|
63
|
-
process_time:
|
64
|
-
background_processing:
|
65
|
-
on_stdout_processing:
|
66
|
-
sleep_procs:
|
67
|
-
terminal_chars_width:
|
68
|
-
terminal_chars_height:
|
69
|
-
terminal_pixels_width:
|
70
|
-
terminal_pixels_height:
|
71
|
-
terminal_term:
|
72
|
-
terminal_modes:
|
60
|
+
after_open_channel_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after opening a channel, for example you could call #detect_prompt or #read_till
|
61
|
+
open_channel_timeout: nil, # timeout to open the channel
|
62
|
+
net_ssh_options: ActiveSupport::HashWithIndifferentAccess.new, # a wrapper for options to pass to Net::SSH.start in case net_ssh is undefined
|
63
|
+
process_time: 0.00001, # how long #process is processing net_ssh#process or sleeping (waiting for something)
|
64
|
+
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
|
65
|
+
on_stdout_processing: 100, # whether to optimize the on_stdout performance by calling #process #optimize_on_stdout-times in case more data arrives
|
66
|
+
sleep_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call instead of Kernel.sleep(), perfect for async hooks
|
67
|
+
terminal_chars_width: 320, # Sets and sends the terminal dimensions during the opening of the channel. It does not send a channel_request on change.
|
68
|
+
terminal_chars_height: 120, # See also https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/connection/channel.rb#L220 section 'def request_pty'
|
69
|
+
terminal_pixels_width: 1920, # See also https://www.ietf.org/rfc/rfc4254.txt section pty-req and section window-change
|
70
|
+
terminal_pixels_height: 1080, #
|
71
|
+
terminal_term: nil, # Sets the terminal term, usually xterm
|
72
|
+
terminal_modes: nil #
|
73
73
|
)
|
74
74
|
|
75
75
|
def options
|
@@ -103,10 +103,10 @@ module Net
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
-
OPTIONS.keys.select {|key| key.to_s.include? "procs"}.each do |name|
|
107
|
-
define_method name.sub("_procs","") do |&blk|
|
106
|
+
OPTIONS.keys.select { |key| key.to_s.include? "procs" }.each do |name|
|
107
|
+
define_method name.sub("_procs", "") do |&blk|
|
108
108
|
id = SecureRandom.uuid
|
109
|
-
|
109
|
+
send(name)[id] = proc { blk.call }
|
110
110
|
id
|
111
111
|
end
|
112
112
|
end
|
@@ -168,12 +168,10 @@ module Net
|
|
168
168
|
# end
|
169
169
|
# cmd("exit")
|
170
170
|
#
|
171
|
-
def with_named_prompt(name)
|
171
|
+
def with_named_prompt(name, &block)
|
172
172
|
raise Error::UndefinedMatch, "unknown named_prompt #{name}" unless named_prompts[name]
|
173
173
|
|
174
|
-
with_prompt(named_prompts[name])
|
175
|
-
yield
|
176
|
-
end
|
174
|
+
with_prompt(named_prompts[name], &block)
|
177
175
|
end
|
178
176
|
|
179
177
|
# tries to detect the prompt
|
@@ -217,17 +215,17 @@ module Net
|
|
217
215
|
# - hard_timeout_factor: nil, true, or a number
|
218
216
|
# - when hard_timeout == true, this will set the hard_timeout as (read_till_hard_timeout_factor * read_till_timeout), defaults to 1.2 = +20%
|
219
217
|
def read_till(prompt: current_prompt, timeout: read_till_timeout, hard_timeout: read_till_hard_timeout, hard_timeout_factor: read_till_hard_timeout_factor, **_opts)
|
220
|
-
raise Error::UndefinedMatch,
|
218
|
+
raise Error::UndefinedMatch, "no prompt given or default_prompt defined" unless prompt
|
219
|
+
|
221
220
|
hard_timeout = (read_till_hard_timeout_factor * timeout) if timeout and hard_timeout == true
|
222
221
|
hard_timeout = nil if hard_timeout == true
|
223
222
|
|
224
223
|
with_prompt(prompt) do
|
225
224
|
::Timeout.timeout(hard_timeout, Error::ReadTillTimeout, "#{current_prompt.inspect} didn't match on #{stdout.inspect} within #{hard_timeout}s") do
|
226
225
|
soft_timeout = Time.now + timeout if timeout
|
227
|
-
until prompt_in_stdout?
|
228
|
-
if timeout and soft_timeout < Time.now
|
229
|
-
|
230
|
-
end
|
226
|
+
until prompt_in_stdout?
|
227
|
+
raise Error::ReadTillTimeout, "#{current_prompt.inspect} didn't match on #{stdout.inspect} within #{timeout}s" if timeout and soft_timeout < Time.now
|
228
|
+
|
231
229
|
process
|
232
230
|
sleep 0.01 # don't race for CPU
|
233
231
|
end
|
@@ -253,7 +251,7 @@ module Net
|
|
253
251
|
end
|
254
252
|
|
255
253
|
def dialog(command, prompt, **opts)
|
256
|
-
opts = opts.clone.merge(prompt:
|
254
|
+
opts = opts.clone.merge(prompt:)
|
257
255
|
cmd(command, **opts)
|
258
256
|
end
|
259
257
|
|
@@ -265,7 +263,7 @@ module Net
|
|
265
263
|
# Hint: 'read' first on purpose as a feature. once you cmd you ignore what happend before. otherwise use read|write directly.
|
266
264
|
# this should avoid many horrible state issues where the prompt is not the last prompt
|
267
265
|
def cmd(command, pre_read: true, rm_prompt: cmd_rm_prompt, rm_command: cmd_rm_command, prompt: current_prompt, minimum_duration: cmd_minimum_duration, **opts)
|
268
|
-
opts = opts.clone.merge(pre_read
|
266
|
+
opts = opts.clone.merge(pre_read:, rm_prompt:, rm_command:, prompt:)
|
269
267
|
if pre_read
|
270
268
|
pre_read_data = read
|
271
269
|
logger.debug { "#cmd ignoring pre-command output: #{pre_read_data.inspect}" } if pre_read_data.present?
|
@@ -291,7 +289,7 @@ module Net
|
|
291
289
|
alias commands cmds
|
292
290
|
|
293
291
|
def rm_command!(output, command, **opts)
|
294
|
-
output[command + cmd_rm_command_tail] =
|
292
|
+
output[command + cmd_rm_command_tail] = "" if rm_command?(**opts) && output[command + cmd_rm_command_tail]
|
295
293
|
end
|
296
294
|
|
297
295
|
# removes the prompt from the given output
|
@@ -299,19 +297,17 @@ module Net
|
|
299
297
|
# for backwards compatibility it also tries to replace the first match of the prompt /(something)\z/
|
300
298
|
# it removes the whole match if no matches are given /something\z/
|
301
299
|
def rm_prompt!(output, prompt: current_prompt, **opts)
|
302
|
-
if rm_prompt?(**opts)
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
output[prompt] = ''
|
314
|
-
end
|
300
|
+
if rm_prompt?(**opts) && (output[prompt])
|
301
|
+
case prompt
|
302
|
+
when String then output[prompt] = ""
|
303
|
+
when Regexp
|
304
|
+
if prompt.names.include?("prompt")
|
305
|
+
output[prompt, "prompt"] = ""
|
306
|
+
else
|
307
|
+
begin
|
308
|
+
output[prompt, 1] = ""
|
309
|
+
rescue IndexError
|
310
|
+
output[prompt] = ""
|
315
311
|
end
|
316
312
|
end
|
317
313
|
end
|
@@ -359,8 +355,8 @@ module Net
|
|
359
355
|
def net_ssh
|
360
356
|
return @net_ssh if @net_ssh
|
361
357
|
|
362
|
-
logger.debug {
|
363
|
-
self.net_ssh = Net::SSH.start(net_ssh_options[:ip] || net_ssh_options[:host] ||
|
358
|
+
logger.debug { "Net:SSH #start" }
|
359
|
+
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)
|
364
360
|
rescue StandardError => error
|
365
361
|
self.net_ssh = nil
|
366
362
|
raise
|
@@ -381,13 +377,13 @@ module Net
|
|
381
377
|
before_open_channel_procs.each { |_name, a_proc| instance_eval(&a_proc) }
|
382
378
|
::Timeout.timeout(open_channel_timeout, Error::OpenChannelTimeout) do
|
383
379
|
net_ssh.open_channel do |new_channel|
|
384
|
-
logger.debug
|
380
|
+
logger.debug "channel is open"
|
385
381
|
self.channel = new_channel
|
386
382
|
new_channel.request_pty(terminal_options) do |_ch, success|
|
387
383
|
raise Error::Pty, "#{host || ip} Failed to open ssh pty" unless success
|
388
384
|
end
|
389
|
-
new_channel.send_channel_request(
|
390
|
-
raise Error::RequestShell,
|
385
|
+
new_channel.send_channel_request("shell") do |_ch, success|
|
386
|
+
raise Error::RequestShell, "Failed to open ssh shell" unless success
|
391
387
|
end
|
392
388
|
new_channel.on_data do |_ch, data|
|
393
389
|
on_stdout(data)
|
@@ -395,9 +391,9 @@ module Net
|
|
395
391
|
# new_channel.on_extended_data do |_ch, type, data| end
|
396
392
|
# new_channel.on_close do end
|
397
393
|
end
|
398
|
-
until channel
|
394
|
+
process until channel
|
399
395
|
end
|
400
|
-
logger.debug
|
396
|
+
logger.debug "channel is ready, running callbacks now"
|
401
397
|
after_open_channel_procs.each { |_name, a_proc| instance_eval(&a_proc) }
|
402
398
|
process
|
403
399
|
self
|
@@ -415,7 +411,7 @@ module Net
|
|
415
411
|
end
|
416
412
|
|
417
413
|
def formatted_net_ssh_options
|
418
|
-
net_ssh_options.symbolize_keys.reject {|k,
|
414
|
+
net_ssh_options.symbolize_keys.reject { |k, _v| [:host, :ip, :user].include?(k) }
|
419
415
|
end
|
420
416
|
|
421
417
|
def rm_prompt?(**opts)
|
@@ -445,7 +441,7 @@ module Net
|
|
445
441
|
pixels_wide: terminal_pixels_width,
|
446
442
|
pixels_high: terminal_pixels_height,
|
447
443
|
modes: terminal_modes
|
448
|
-
}.reject {|
|
444
|
+
}.reject { |_k, v| v.nil? }
|
449
445
|
end
|
450
446
|
end
|
451
447
|
end
|
@@ -457,8 +453,9 @@ end
|
|
457
453
|
|
458
454
|
class Net::SSH::Connection::Session
|
459
455
|
attr_accessor :cli_channels
|
456
|
+
|
460
457
|
def cli(**opts)
|
461
|
-
cli_session = Net::SSH::CLI::Session.new({ net_ssh: self }.merge(opts))
|
458
|
+
cli_session = Net::SSH::CLI::Session.new(**{ net_ssh: self }.merge(opts))
|
462
459
|
cli_session.open_channel
|
463
460
|
cli_session
|
464
461
|
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: 1.9.
|
4
|
+
version: 1.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fabian Stillhart
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bump
|
@@ -140,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: '0'
|
142
142
|
requirements: []
|
143
|
-
rubygems_version: 3.3.
|
143
|
+
rubygems_version: 3.3.26
|
144
144
|
signing_key:
|
145
145
|
specification_version: 4
|
146
146
|
summary: 'Net::SSH::CLI: A library to handle CLI Sessions'
|