shells 0.1.23 → 0.2.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 +8 -10
- data/bin/console +2 -4
- data/bin/test-client +202 -0
- data/lib/shells.rb +6 -10
- data/lib/shells/bash_common.rb +17 -7
- data/lib/shells/errors.rb +25 -4
- data/lib/shells/pf_sense_common.rb +85 -273
- data/lib/shells/pf_shell_wrapper.rb +270 -0
- data/lib/shells/serial_bash_shell.rb +65 -0
- data/lib/shells/{pf_sense_serial_session.rb → serial_pf_sense_shell.rb} +16 -14
- data/lib/shells/{serial_session.rb → serial_shell.rb} +66 -78
- data/lib/shells/shell_base.rb +17 -867
- data/lib/shells/shell_base/debug.rb +37 -0
- data/lib/shells/shell_base/exec.rb +175 -0
- data/lib/shells/shell_base/hooks.rb +83 -0
- data/lib/shells/shell_base/input.rb +50 -0
- data/lib/shells/shell_base/interface.rb +149 -0
- data/lib/shells/shell_base/options.rb +111 -0
- data/lib/shells/shell_base/output.rb +217 -0
- data/lib/shells/shell_base/prompt.rb +141 -0
- data/lib/shells/shell_base/regex_escape.rb +23 -0
- data/lib/shells/shell_base/run.rb +188 -0
- data/lib/shells/shell_base/sync.rb +24 -0
- data/lib/shells/ssh_bash_shell.rb +71 -0
- data/lib/shells/{pf_sense_ssh_session.rb → ssh_pf_sense_shell.rb} +16 -14
- data/lib/shells/ssh_shell.rb +215 -0
- data/lib/shells/version.rb +1 -1
- data/shells.gemspec +1 -0
- metadata +35 -6
- data/lib/shells/ssh_session.rb +0 -249
@@ -0,0 +1,23 @@
|
|
1
|
+
module Shells
|
2
|
+
class ShellBase
|
3
|
+
private
|
4
|
+
|
5
|
+
def regex_escape(text)
|
6
|
+
text
|
7
|
+
.gsub('\\', '\\\\')
|
8
|
+
.gsub('[', '\\[')
|
9
|
+
.gsub(']', '\\]')
|
10
|
+
.gsub('(', '\\(')
|
11
|
+
.gsub(')', '\\)')
|
12
|
+
.gsub('.', '\\.')
|
13
|
+
.gsub('*', '\\*')
|
14
|
+
.gsub('+', '\\+')
|
15
|
+
.gsub('?', '\\?')
|
16
|
+
.gsub('{', '\\{')
|
17
|
+
.gsub('}', '\\}')
|
18
|
+
.gsub('$', '\\$')
|
19
|
+
.gsub('^', '\\^')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Shells
|
2
|
+
class ShellBase
|
3
|
+
|
4
|
+
attr_accessor :run_flag
|
5
|
+
private :run_flag, :run_flag=
|
6
|
+
|
7
|
+
add_hook :on_init do |sh|
|
8
|
+
sh.instance_eval do
|
9
|
+
self.run_flag = false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Is the shell currently running?
|
15
|
+
def running?
|
16
|
+
run_flag
|
17
|
+
end
|
18
|
+
|
19
|
+
# the thread used to run the session.
|
20
|
+
attr_accessor :session_thread
|
21
|
+
private :session_thread, :session_thread=
|
22
|
+
|
23
|
+
# track exceptions raised during session execution.
|
24
|
+
attr_accessor :session_exception
|
25
|
+
private :session_exception, :session_exception=
|
26
|
+
|
27
|
+
##
|
28
|
+
# Set to true to ignore IO errors.
|
29
|
+
attr_accessor :ignore_io_error
|
30
|
+
protected :ignore_io_error, :ignore_io_error=
|
31
|
+
|
32
|
+
add_hook :on_before_run do |sh|
|
33
|
+
sh.instance_eval do
|
34
|
+
self.session_exception = nil
|
35
|
+
self.ignore_io_error = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
add_hook :on_after_run do |sh|
|
40
|
+
sh.instance_eval do
|
41
|
+
if session_thread&.status
|
42
|
+
session_thread.exit
|
43
|
+
end
|
44
|
+
self.session_thread = nil
|
45
|
+
self.ignore_io_error = false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Runs a shell session.
|
51
|
+
#
|
52
|
+
# The block provided will be run asynchronously with the shell.
|
53
|
+
#
|
54
|
+
# Returns the shell instance.
|
55
|
+
def run(&block)
|
56
|
+
sync do
|
57
|
+
raise Shells::AlreadyRunning if running?
|
58
|
+
self.run_flag = true
|
59
|
+
end
|
60
|
+
|
61
|
+
begin
|
62
|
+
run_hook :on_before_run
|
63
|
+
|
64
|
+
debug 'Connecting...'
|
65
|
+
connect
|
66
|
+
|
67
|
+
debug 'Starting output buffering...'
|
68
|
+
buffer_output
|
69
|
+
|
70
|
+
debug 'Starting session thread...'
|
71
|
+
self.session_thread = Thread.start(self) do |sh|
|
72
|
+
begin
|
73
|
+
begin
|
74
|
+
debug 'Executing setup...'
|
75
|
+
sh.instance_eval { setup }
|
76
|
+
debug 'Executing block...'
|
77
|
+
block.call sh
|
78
|
+
ensure
|
79
|
+
debug 'Executing teardown...'
|
80
|
+
sh.instance_eval { teardown }
|
81
|
+
end
|
82
|
+
rescue Shells::QuitNow
|
83
|
+
# just exit the session.
|
84
|
+
rescue =>e
|
85
|
+
# if the exception is handled by the hook no further processing is required, otherwise we store the exception
|
86
|
+
# to propagate it in the main thread.
|
87
|
+
unless sh.run_hook(:on_exception, e) == :break
|
88
|
+
sh.sync { sh.instance_eval { self.session_exception = e } }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# process the input buffer while the thread is alive and the shell is active.
|
94
|
+
debug 'Entering IO loop...'
|
95
|
+
io_loop do
|
96
|
+
if active?
|
97
|
+
begin
|
98
|
+
if session_thread.status # not dead
|
99
|
+
# process input from the session.
|
100
|
+
unless wait_for_output
|
101
|
+
inp = next_input
|
102
|
+
if inp
|
103
|
+
send_data inp
|
104
|
+
self.wait_for_output = (options[:unbuffered_input] == :echo)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# continue running the IO loop
|
109
|
+
true
|
110
|
+
elsif session_exception
|
111
|
+
# propagate the exception.
|
112
|
+
raise session_exception.class, session_exception.message, session_exception.backtrace
|
113
|
+
else
|
114
|
+
# the thread has exited, but no exception exists.
|
115
|
+
# regardless, the IO loop should now exit.
|
116
|
+
false
|
117
|
+
end
|
118
|
+
rescue IOError
|
119
|
+
if ignore_io_error
|
120
|
+
# we were (sort of) expecting the IO error, so just tell the IO loop to exit.
|
121
|
+
false
|
122
|
+
else
|
123
|
+
raise
|
124
|
+
end
|
125
|
+
end
|
126
|
+
else
|
127
|
+
# the shell session is no longer active, tell the IO loop to exit.
|
128
|
+
false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
rescue
|
132
|
+
# when an error occurs, try to disconnect, but ignore any further errors.
|
133
|
+
begin
|
134
|
+
debug 'Disconnecting...'
|
135
|
+
disconnect
|
136
|
+
rescue
|
137
|
+
# ignore
|
138
|
+
end
|
139
|
+
raise
|
140
|
+
else
|
141
|
+
# when no error occurs, try to disconnect and propagate any errors (unless we are ignoring IO errors).
|
142
|
+
begin
|
143
|
+
debug 'Disconnecting...'
|
144
|
+
disconnect
|
145
|
+
rescue IOError
|
146
|
+
raise unless ignore_io_error
|
147
|
+
end
|
148
|
+
ensure
|
149
|
+
# cleanup
|
150
|
+
run_hook :on_after_run
|
151
|
+
self.run_flag = false
|
152
|
+
end
|
153
|
+
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
protected
|
158
|
+
|
159
|
+
##
|
160
|
+
# Adds code to be run when an exception occurs.
|
161
|
+
#
|
162
|
+
# This code will receive the shell as the first argument and the exception as the second.
|
163
|
+
# If it handles the exception it should return :break.
|
164
|
+
#
|
165
|
+
# on_exception do |shell, ex|
|
166
|
+
# if ex.is_a?(MyExceptionType)
|
167
|
+
# ...
|
168
|
+
# :break
|
169
|
+
# else
|
170
|
+
# false
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# You can also pass the name of a static method.
|
175
|
+
#
|
176
|
+
# def self.some_exception_handler(shell, ex)
|
177
|
+
# ...
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
# on_exception :some_exception_handler
|
181
|
+
#
|
182
|
+
def self.on_exception(proc = nil, &block)
|
183
|
+
add_hook :on_exception, proc, &block
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shells
|
2
|
+
class ShellBase
|
3
|
+
|
4
|
+
attr_accessor :thread_lock
|
5
|
+
private :thread_lock, :thread_lock=
|
6
|
+
|
7
|
+
add_hook :on_init do |sh|
|
8
|
+
puts 'Initializing...'
|
9
|
+
sh.instance_eval do
|
10
|
+
self.thread_lock = Mutex.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
##
|
17
|
+
# Synchronizes actions between shell threads.
|
18
|
+
def sync(&block)
|
19
|
+
thread_lock.synchronize &block
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'shells/ssh_shell'
|
2
|
+
require 'shells/bash_common'
|
3
|
+
|
4
|
+
module Shells
|
5
|
+
##
|
6
|
+
# Executes a Bash session with an SSH host.
|
7
|
+
#
|
8
|
+
# The default setup of this class should work well with any bash-like shell.
|
9
|
+
#
|
10
|
+
# Valid options:
|
11
|
+
# +host+::
|
12
|
+
# The name or IP address of the host to connect to. Defaults to 'localhost'.
|
13
|
+
# +port+::
|
14
|
+
# The port on the host to connect to. Defaults to 22.
|
15
|
+
# +user+::
|
16
|
+
# The user to login with. This option is required.
|
17
|
+
# +password+::
|
18
|
+
# The password to login with.
|
19
|
+
# If our public key is an authorized key on the host, the password is ignored for connection.
|
20
|
+
# The #sudo_exec method for bash-like shells will also use this password for elevation.
|
21
|
+
# +prompt+::
|
22
|
+
# The prompt used to determine when processes finish execution.
|
23
|
+
# Defaults to '~~#', but if that doesn't work for some reason because it is valid output from one or more
|
24
|
+
# commands, you can change it to something else. It must be unique and cannot contain certain characters.
|
25
|
+
# The characters you should avoid are !, $, \, /, ", and ' because no attempt is made to escape them and the
|
26
|
+
# resulting prompt can very easily become something else entirely. If they are provided, they will be
|
27
|
+
# replaced to protect the shell from getting stuck.
|
28
|
+
# +quit+::
|
29
|
+
# If set, this defines the command to execute when quitting the session.
|
30
|
+
# The default is "exit" which will probably work most of the time.
|
31
|
+
# +retrieve_exit_code+::
|
32
|
+
# If set to a non-false value, then the default behavior will be to retrieve the exit code from the shell after
|
33
|
+
# executing a command. If set to a false or nil value, the default behavior will be to ignore the exit code
|
34
|
+
# from the shell. When retrieved, the exit code is stored in the +last_exit_code+ property.
|
35
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
36
|
+
# +on_non_zero_exit_code+::
|
37
|
+
# If set to :ignore (the default) then non-zero exit codes will not cause errors. You will still be able to check
|
38
|
+
# the +last_exit_code+ property to determine if the command was successful.
|
39
|
+
# If set to :raise then non-zero exit codes will cause a Shells::NonZeroExitCode to be raised when a command exits
|
40
|
+
# with a non-zero return value.
|
41
|
+
# This option only comes into play when +retrieve_exit_code+ is set to a non-false value.
|
42
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
43
|
+
# +silence_timeout+::
|
44
|
+
# When a command is executing, this is the maximum amount of time to wait for any feedback from the shell.
|
45
|
+
# If set to 0 (or less) there is no timeout.
|
46
|
+
# Unlike +command_timeout+ this value resets every time we receive feedback.
|
47
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
48
|
+
# +command_timeout+::
|
49
|
+
# When a command is executing, this is the maximum amount of time to wait for the command to finish.
|
50
|
+
# If set to 0 (or less) there is no timeout.
|
51
|
+
# Unlike +silence_timeout+ this value does not reset when we receive feedback.
|
52
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
53
|
+
# +connect_timeout+::
|
54
|
+
# This is the maximum amount of time to wait for the initial connection to the SSH shell.
|
55
|
+
#
|
56
|
+
# Shells::SshBashShell.new(
|
57
|
+
# host: '10.10.10.10',
|
58
|
+
# user: 'somebody',
|
59
|
+
# password: 'super-secret'
|
60
|
+
# ) do |shell|
|
61
|
+
# shell.exec('cd /usr/local/bin')
|
62
|
+
# user_bin_files = shell.exec('ls -A1').split("\n")
|
63
|
+
# @app_is_installed = user_bin_files.include?('my_app')
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
class SshBashShell < SshShell
|
67
|
+
|
68
|
+
include BashCommon
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'shells/
|
1
|
+
require 'shells/ssh_bash_shell'
|
2
2
|
require 'shells/pf_sense_common'
|
3
3
|
|
4
4
|
module Shells
|
@@ -29,25 +29,27 @@ module Shells
|
|
29
29
|
# +connect_timeout+::
|
30
30
|
# This is the maximum amount of time to wait for the initial connection to the SSH shell.
|
31
31
|
#
|
32
|
-
# Shells::
|
32
|
+
# Shells::SshPfSenseShell.new(
|
33
33
|
# host: '10.10.10.10',
|
34
34
|
# user: 'somebody',
|
35
35
|
# password: 'super-secret'
|
36
36
|
# ) do |shell|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
37
|
+
# shell.pf_shell do |shell|
|
38
|
+
# cfg = shell.get_config_section("aliases")
|
39
|
+
# cfg["alias"] ||= []
|
40
|
+
# cfg["alias"] << {
|
41
|
+
# :name => "MY_NETWORK",
|
42
|
+
# :type => "network",
|
43
|
+
# :address => "192.168.1.0/24",
|
44
|
+
# :descr => "My home network",
|
45
|
+
# :details => "Created #{Time.now.to_s}"
|
46
|
+
# }
|
47
|
+
# shell.set_config_section("aliases", cfg, "Add home network")
|
48
|
+
# shell.apply_filter_config
|
49
|
+
# end
|
48
50
|
# end
|
49
51
|
#
|
50
|
-
class
|
52
|
+
class SshPfSenseShell < SshBashShell
|
51
53
|
|
52
54
|
include PfSenseCommon
|
53
55
|
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'shells/shell_base'
|
3
|
+
|
4
|
+
module Shells
|
5
|
+
##
|
6
|
+
# Executes an SSH session with a host.
|
7
|
+
#
|
8
|
+
# Valid options:
|
9
|
+
# +host+::
|
10
|
+
# The name or IP address of the host to connect to. Defaults to 'localhost'.
|
11
|
+
# +port+::
|
12
|
+
# The port on the host to connect to. Defaults to 22.
|
13
|
+
# +user+::
|
14
|
+
# The user to login with. This option is required.
|
15
|
+
# +password+::
|
16
|
+
# The password to login with.
|
17
|
+
# If our public key is an authorized key on the host, the password is ignored for connection.
|
18
|
+
# The #sudo_exec method for bash-like shells will also use this password for elevation.
|
19
|
+
# +prompt+::
|
20
|
+
# The prompt used to determine when processes finish execution.
|
21
|
+
# +shell+::
|
22
|
+
# If set to :shell, then the default shell is executed. This is the default value.
|
23
|
+
# If set to :none, then no shell is executed, but a PTY is still created.
|
24
|
+
# If set to :no_pty, then no shell is executed and no PTY is created.
|
25
|
+
# If set to anything else, it is assumed to be the executable path to the shell you want to run.
|
26
|
+
# +quit+::
|
27
|
+
# If set, this defines the command to execute when quitting the session.
|
28
|
+
# The default is "exit" which will probably work most of the time.
|
29
|
+
# +retrieve_exit_code+::
|
30
|
+
# If set to a non-false value, then the default behavior will be to retrieve the exit code from the shell after
|
31
|
+
# executing a command. If set to a false or nil value, the default behavior will be to ignore the exit code
|
32
|
+
# from the shell. When retrieved, the exit code is stored in the +last_exit_code+ property.
|
33
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
34
|
+
# +on_non_zero_exit_code+::
|
35
|
+
# If set to :ignore (the default) then non-zero exit codes will not cause errors. You will still be able to check
|
36
|
+
# the +last_exit_code+ property to determine if the command was successful.
|
37
|
+
# If set to :raise then non-zero exit codes will cause a Shells::NonZeroExitCode to be raised when a command exits
|
38
|
+
# with a non-zero return value.
|
39
|
+
# This option only comes into play when +retrieve_exit_code+ is set to a non-false value.
|
40
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
41
|
+
# +silence_timeout+::
|
42
|
+
# When a command is executing, this is the maximum amount of time to wait for any feedback from the shell.
|
43
|
+
# If set to 0 (or less) there is no timeout.
|
44
|
+
# Unlike +command_timeout+ this value resets every time we receive feedback.
|
45
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
46
|
+
# +command_timeout+::
|
47
|
+
# When a command is executing, this is the maximum amount of time to wait for the command to finish.
|
48
|
+
# If set to 0 (or less) there is no timeout.
|
49
|
+
# Unlike +silence_timeout+ this value does not reset when we receive feedback.
|
50
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
51
|
+
# +connect_timeout+::
|
52
|
+
# This is the maximum amount of time to wait for the initial connection to the SSH shell.
|
53
|
+
#
|
54
|
+
# Shells::SshShell.new(
|
55
|
+
# host: '10.10.10.10',
|
56
|
+
# user: 'somebody',
|
57
|
+
# password: 'super-secret'
|
58
|
+
# ) do |shell|
|
59
|
+
# shell.exec('cd /usr/local/bin')
|
60
|
+
# user_bin_files = shell.exec('ls -A1').split("\n")
|
61
|
+
# @app_is_installed = user_bin_files.include?('my_app')
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
class SshShell < Shells::ShellBase
|
65
|
+
|
66
|
+
##
|
67
|
+
# The error raised when we failed to request a PTY.
|
68
|
+
class FailedToRequestPty < Shells::ShellError
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# The error raised when we fail to start the shell on the PTY.
|
74
|
+
class FailedToStartShell < Shells::ShellError
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_accessor :ssh, :channel
|
79
|
+
private :ssh, :ssh=, :channel, :channel=
|
80
|
+
|
81
|
+
add_hook :on_before_run do |sh|
|
82
|
+
sh.instance_eval do
|
83
|
+
self.ssh = nil
|
84
|
+
self.channel = nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
add_hook :on_after_run do |sh|
|
89
|
+
sh.instance_eval do
|
90
|
+
self.ssh = nil
|
91
|
+
self.channel = nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def validate_options #:nodoc:
|
98
|
+
options[:host] ||= 'localhost'
|
99
|
+
options[:port] ||= 22
|
100
|
+
options[:shell] ||= :shell
|
101
|
+
options[:quit] ||= 'exit'
|
102
|
+
options[:connect_timeout] ||= 5
|
103
|
+
|
104
|
+
raise InvalidOption, 'Missing host.' if options[:host].to_s.strip == ''
|
105
|
+
raise InvalidOption, 'Missing user.' if options[:user].to_s.strip == ''
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def connect #:nodoc:
|
110
|
+
|
111
|
+
debug 'Connecting to SSH host...'
|
112
|
+
self.ssh = Net::SSH.start(
|
113
|
+
options[:host],
|
114
|
+
options[:user],
|
115
|
+
password: options[:password],
|
116
|
+
port: options[:port],
|
117
|
+
non_interactive: true,
|
118
|
+
timeout: options[:connect_timeout]
|
119
|
+
)
|
120
|
+
debug ' > connected'
|
121
|
+
|
122
|
+
opened = false
|
123
|
+
|
124
|
+
debug 'Opening channel...'
|
125
|
+
self.channel = ssh.open_channel do |ch|
|
126
|
+
opened = true
|
127
|
+
end
|
128
|
+
|
129
|
+
io_loop { !opened }
|
130
|
+
debug ' > opened'
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
def setup #:nodoc:
|
135
|
+
done = false
|
136
|
+
unless options[:shell] == :no_pty
|
137
|
+
debug 'Acquiring PTY...'
|
138
|
+
channel.request_pty do |_, success|
|
139
|
+
raise FailedToRequestPty unless success
|
140
|
+
debug ' > acquired'
|
141
|
+
done = true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
until done
|
146
|
+
sleep 0.0001
|
147
|
+
end
|
148
|
+
|
149
|
+
done = false
|
150
|
+
unless [:no_pty,:none].include?(options[:shell])
|
151
|
+
debug 'Starting shell...'
|
152
|
+
# pick a method to start the shell with.
|
153
|
+
meth = (options[:shell] == :shell) ? :send_channel_request : :exec
|
154
|
+
channel.send(meth, options[:shell].to_s) do |_, success|
|
155
|
+
raise FailedToStartShell unless success
|
156
|
+
debug ' > started'
|
157
|
+
done = true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
until done
|
162
|
+
sleep 0.0001
|
163
|
+
end
|
164
|
+
|
165
|
+
debug 'Calling setup_prompt...'
|
166
|
+
setup_prompt
|
167
|
+
debug ' > prompt setup'
|
168
|
+
end
|
169
|
+
|
170
|
+
def disconnect #:nodoc:
|
171
|
+
debug 'Marking channel for closure...'
|
172
|
+
channel.close
|
173
|
+
debug ' > marked'
|
174
|
+
debug 'Closing SSH connection...'
|
175
|
+
ssh.close
|
176
|
+
debug ' > closed'
|
177
|
+
end
|
178
|
+
|
179
|
+
def send_data(data) #:nodoc:
|
180
|
+
channel.send_data data
|
181
|
+
debug "Sent: (#{data.size} bytes) #{(data.size > 32 ? (data[0..30] + '...') : data).inspect}"
|
182
|
+
end
|
183
|
+
|
184
|
+
def stdout_received(&block) #:nodoc:
|
185
|
+
channel.on_data do |_,data|
|
186
|
+
debug "Received: (#{data.size} bytes) #{(data.size > 32 ? (data[0..30] + '...') : data).inspect}"
|
187
|
+
block.call data
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def stderr_received(&block) #:nodoc:
|
192
|
+
channel.on_extended_data do |_, type, data|
|
193
|
+
if type == 1
|
194
|
+
debug "Received: (#{data.size} bytes) [E] #{(data.size > 32 ? (data[0..30] + '...') : data).inspect}"
|
195
|
+
block.call data
|
196
|
+
else
|
197
|
+
debug "Received: (#{data.size} bytes) [#{type}] #{(data.size > 32 ? (data[0..30] + '...') : data).inspect}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def active?
|
203
|
+
channel&.active?
|
204
|
+
end
|
205
|
+
|
206
|
+
def io_loop(&block)
|
207
|
+
shell = self
|
208
|
+
ssh&.loop(0.000001) do |_|
|
209
|
+
shell.instance_eval &block
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|