Module: Net::SSH::CLI
- Included in:
- Session
- Defined in:
- lib/net/ssh/cli.rb,
lib/net/ssh/cli/version.rb
Defined Under Namespace
Constant Summary collapse
- OPTIONS =
ActiveSupport::HashWithIndifferentAccess.new( default_prompt: /\n?^(\S+@.*)\z/, # the default prompt to search for cmd_rm_prompt: false, # whether the prompt should be removed in the output of #cmd cmd_rm_command: false, # whether the given command should be removed in the output of #cmd read_till_timeout: nil, # timeout for #read_till to find the match named_prompts: ActiveSupport::HashWithIndifferentAccess.new, # you can used named prompts for #with_prompt {} before_on_stdout_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before data arrives from the underlying connection after_on_stdout_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after data arrives from the underlying connection before_open_channel_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call before opening a channel after_open_channel_procs: ActiveSupport::HashWithIndifferentAccess.new, # procs to call after opening a channel, for example you could call #detect_prompt or #read_till open_channel_timeout: nil, # timeout to open the channel net_ssh_options: ActiveSupport::HashWithIndifferentAccess.new, # a wrapper for options to pass to Net::SSH.start in case net_ssh is undefined process_time: 0.00001, # how long #process is processing net_ssh#process or sleeping (waiting for something) 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 )
- VERSION =
'1.0.0'
Instance Attribute Summary collapse
-
#channel ⇒ Object
Returns the value of attribute channel.
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#net_ssh ⇒ Object
(also: #proxy)
NET::SSH.
-
#stdout ⇒ Object
Returns the value of attribute stdout.
Class Method Summary collapse
-
.start(**opts) ⇒ Object
Example net_ssh = Net::SSH.start(“localhost”) net_ssh_cli = Net::SSH::CLI.start(net_ssh: net_ssh) net_ssh_cli.cmd “cat /etc/passwd” => “root:x:0:0:root:/root:/bin/bashn…”.
Instance Method Summary collapse
- #close_channel ⇒ Object
-
#cmd(command, pre_read: true, rm_prompt: cmd_rm_prompt, rm_command: cmd_rm_command, prompt: current_prompt, **opts) ⇒ Object
(also: #command, #exec)
‘read’ first on purpuse as a feature.
- #cmds(*commands, **opts) ⇒ Object (also: #commands)
-
#current_prompt ⇒ Object
fancy prompt|prompt handling methods.
- #detect_prompt(seconds: 3) ⇒ Object
- #dialog(command, prompt, **opts) ⇒ Object
- #host ⇒ Object (also: #hostname, #to_s)
- #initialize(**opts) ⇒ Object
- #on_stdout(data) ⇒ Object
-
#open_channel ⇒ Object
cli_channel.
- #options ⇒ Object
-
#options!(**opts) ⇒ Object
don’t even think about nesting hashes here.
- #options=(opts) ⇒ Object
-
#process(time = process_time) ⇒ Object
have a deep look at the source of Net::SSH session#process github.com/net-ssh/net-ssh/blob/dd13dd44d68b7fa82d4ca9a3bbe18e30c855f1d2/lib/net/ssh/connection/session.rb#L227 session#loop github.com/net-ssh/net-ssh/blob/dd13dd44d68b7fa82d4ca9a3bbe18e30c855f1d2/lib/net/ssh/connection/session.rb#L179 because the (cli) channel stays open, we always need to ensure that the ssh layer gets “processed” further.
- #read ⇒ Object
- #read_for(seconds:) ⇒ Object
- #read_till(prompt: current_prompt, timeout: read_till_timeout, **_opts) ⇒ Object
- #rm_command!(output, command, **opts) ⇒ Object
- #rm_prompt!(output, **opts) ⇒ Object
- #stdout! ⇒ Object
- #with_named_prompt(name) ⇒ Object
-
#with_prompt(prompt) ⇒ Object
prove a block where the default prompt changes.
- #write(content = String.new) ⇒ Object (also: #stdin)
- #write_n(content = String.new) ⇒ Object
Instance Attribute Details
#channel ⇒ Object
Returns the value of attribute channel.
37 38 39 |
# File 'lib/net/ssh/cli.rb', line 37 def channel @channel end |
#logger ⇒ Object
Returns the value of attribute logger.
37 38 39 |
# File 'lib/net/ssh/cli.rb', line 37 def logger @logger end |
#net_ssh ⇒ Object Also known as: proxy
NET::SSH
236 237 238 |
# File 'lib/net/ssh/cli.rb', line 236 def net_ssh @net_ssh end |
#stdout ⇒ Object
Returns the value of attribute stdout.
37 38 39 |
# File 'lib/net/ssh/cli.rb', line 37 def stdout @stdout end |
Class Method Details
.start(**opts) ⇒ Object
Example net_ssh = Net::SSH.start(“localhost”) net_ssh_cli = Net::SSH::CLI.start(net_ssh: net_ssh) net_ssh_cli.cmd “cat /etc/passwd”
> “root:x:0:0:root:/root:/bin/bashn…”
27 28 29 |
# File 'lib/net/ssh/cli.rb', line 27 def self.start(**opts) Net::SSH::CLI::Session.new(**opts) end |
Instance Method Details
#close_channel ⇒ Object
283 284 285 286 |
# File 'lib/net/ssh/cli.rb', line 283 def close_channel net_ssh&.cleanup_channel(channel) if channel self.channel = nil end |
#cmd(command, pre_read: true, rm_prompt: cmd_rm_prompt, rm_command: cmd_rm_command, prompt: current_prompt, **opts) ⇒ Object Also known as: command, exec
‘read’ first on purpuse as a feature. once you cmd you ignore what happend before. otherwise use read|write directly. this should avoid many horrible state issues where the prompt is not the last prompt
194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/net/ssh/cli.rb', line 194 def cmd(command, pre_read: true, rm_prompt: cmd_rm_prompt, rm_command: cmd_rm_command, prompt: current_prompt, **opts) opts = opts.clone.merge(pre_read: pre_read, rm_prompt: rm_prompt, rm_command: rm_command, prompt: prompt) if pre_read pre_read_data = read logger.debug { "#cmd ignoring pre-command output: #{pre_read_data.inspect}" } if pre_read_data.present? end write_n command output = read_till(opts) rm_prompt!(output, opts) rm_command!(output, command, opts) output end |
#cmds(*commands, **opts) ⇒ Object Also known as: commands
209 210 211 |
# File 'lib/net/ssh/cli.rb', line 209 def cmds(*commands, **opts) commands.flatten.map { |command| [command, cmd(command, **opts)] } end |
#current_prompt ⇒ Object
fancy prompt|prompt handling methods
132 133 134 |
# File 'lib/net/ssh/cli.rb', line 132 def current_prompt with_prompts[-1] || default_prompt end |
#detect_prompt(seconds: 3) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/net/ssh/cli.rb', line 144 def detect_prompt(seconds: 3) write_n future = Time.now + seconds while future > Time.now process sleep 0.1 end self.default_prompt = read[/\n?^.*\z/] raise Error::PromptDetection, "couldn't detect a prompt" unless default_prompt.present? default_prompt end |
#dialog(command, prompt, **opts) ⇒ Object
187 188 189 190 |
# File 'lib/net/ssh/cli.rb', line 187 def dialog(command, prompt, **opts) opts = opts.clone.merge(prompt: prompt) cmd(command, opts) end |
#host ⇒ Object Also known as: hostname, to_s
227 228 229 |
# File 'lib/net/ssh/cli.rb', line 227 def host @net_ssh&.host end |
#initialize(**opts) ⇒ Object
31 32 33 34 35 |
# File 'lib/net/ssh/cli.rb', line 31 def initialize(**opts) .merge!(opts) self.net_ssh = .delete(:net_ssh) self.logger = .delete(:logger) || Logger.new(STDOUT, level: Logger::WARN) end |
#on_stdout(data) ⇒ Object
102 103 104 105 106 107 108 |
# File 'lib/net/ssh/cli.rb', line 102 def on_stdout(data) before_on_stdout_procs.each { |_name, a_proc| instance_eval(&a_proc) } stdout << data after_on_stdout_procs.each { |_name, a_proc| instance_eval(&a_proc) } process # if we receive data, we probably receive more - improves performance stdout end |
#open_channel ⇒ Object
cli_channel
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/net/ssh/cli.rb', line 257 def open_channel # cli_channel before_open_channel_procs.each { |_name, a_proc| instance_eval(&a_proc) } ::Timeout.timeout(open_channel_timeout, Error::OpenChannelTimeout) do net_ssh.open_channel do |new_channel| logger.debug 'channel is open' self.channel = new_channel new_channel.request_pty do |_ch, success| raise Error::Pty, "#{host || ip} Failed to open ssh pty" unless success end new_channel.send_channel_request('shell') do |_ch, success| raise Error::RequestShell, 'Failed to open ssh shell' unless success end new_channel.on_data do |_ch, data| on_stdout(data) end # new_channel.on_extended_data do |_ch, type, data| end # new_channel.on_close do end end until channel do process end end logger.debug 'channel is ready, running callbacks now' after_open_channel_procs.each { |_name, a_proc| instance_eval(&a_proc) } process self end |
#options ⇒ Object
55 56 57 58 59 60 61 62 63 |
# File 'lib/net/ssh/cli.rb', line 55 def @options ||= begin opts = OPTIONS.clone opts.each do |key, value| opts[key] = value.clone if value.is_a?(Hash) end opts end end |
#options!(**opts) ⇒ Object
don’t even think about nesting hashes here
66 67 68 |
# File 'lib/net/ssh/cli.rb', line 66 def (**opts) .merge!(opts) end |
#options=(opts) ⇒ Object
70 71 72 |
# File 'lib/net/ssh/cli.rb', line 70 def (opts) @options = ActiveSupport::HashWithIndifferentAccess.new(opts) end |
#process(time = process_time) ⇒ Object
have a deep look at the source of Net::SSH session#process github.com/net-ssh/net-ssh/blob/dd13dd44d68b7fa82d4ca9a3bbe18e30c855f1d2/lib/net/ssh/connection/session.rb#L227 session#loop github.com/net-ssh/net-ssh/blob/dd13dd44d68b7fa82d4ca9a3bbe18e30c855f1d2/lib/net/ssh/connection/session.rb#L179 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.
251 252 253 254 255 |
# File 'lib/net/ssh/cli.rb', line 251 def process(time = process_time) background_processing? ? sleep(time) : net_ssh.process(time) rescue IOError => error raise Error, error. end |
#read ⇒ Object
122 123 124 125 126 127 |
# File 'lib/net/ssh/cli.rb', line 122 def read process var = stdout! logger.debug("#read: \n#{var}") var end |
#read_for(seconds:) ⇒ Object
182 183 184 185 |
# File 'lib/net/ssh/cli.rb', line 182 def read_for(seconds:) process(seconds) read end |
#read_till(prompt: current_prompt, timeout: read_till_timeout, **_opts) ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/net/ssh/cli.rb', line 168 def read_till(prompt: current_prompt, timeout: read_till_timeout, **_opts) raise Error::UndefinedMatch, 'no prompt given or default_prompt defined' unless prompt ::Timeout.timeout(timeout, Error::ReadTillTimeout.new("output did not prompt #{prompt.inspect} within #{timeout}")) do with_prompt(prompt) do until stdout[current_prompt] do process sleep 0.1 end end end read end |
#rm_command!(output, command, **opts) ⇒ Object
214 215 216 |
# File 'lib/net/ssh/cli.rb', line 214 def rm_command!(output, command, **opts) output[command + "\n"] = '' if rm_command?(opts) && output[command + "\n"] end |
#rm_prompt!(output, **opts) ⇒ Object
218 219 220 221 222 223 224 225 |
# File 'lib/net/ssh/cli.rb', line 218 def rm_prompt!(output, **opts) if rm_prompt?(opts) prompt = opts[:prompt] || current_prompt if output[prompt] prompt.is_a?(Regexp) ? output[prompt, 1] = '' : output[prompt] = '' end end end |
#stdout! ⇒ Object
96 97 98 99 100 |
# File 'lib/net/ssh/cli.rb', line 96 def stdout! var = stdout self.stdout = String.new var end |
#with_named_prompt(name) ⇒ Object
136 137 138 139 140 141 142 |
# File 'lib/net/ssh/cli.rb', line 136 def with_named_prompt(name) raise Error::UndefinedMatch, "unknown named_prompt #{name}" unless named_prompts[name] with_prompt(named_prompts[name]) do yield end end |
#with_prompt(prompt) ⇒ Object
prove a block where the default prompt changes
158 159 160 161 162 163 164 165 166 |
# File 'lib/net/ssh/cli.rb', line 158 def with_prompt(prompt) logger.debug { "#with_prompt: #{current_prompt.inspect} => #{prompt.inspect}" } with_prompts << prompt yield prompt ensure with_prompts.delete_at(-1) logger.debug { "#with_prompt: => #{current_prompt.inspect}" } end |
#write(content = String.new) ⇒ Object Also known as: stdin
110 111 112 113 114 115 |
# File 'lib/net/ssh/cli.rb', line 110 def write(content = String.new) logger.debug { "#write #{content.inspect}" } channel.send_data content process content end |
#write_n(content = String.new) ⇒ Object
118 119 120 |
# File 'lib/net/ssh/cli.rb', line 118 def write_n(content = String.new) write content + "\n" end |