Module: EventMachine::Ssh::Connection::Channel::Interactive

Includes:
EventMachine::Ssh::Callbacks
Defined in:
lib/em-ssh/connection/channel/interactive.rb

Overview

This module adds functionality to any channel it extends. It mainly provides functionality to help interactive behaviour on said channel.

  • #send_data (improved) that can append ‘line terminators’.

  • #wait_for - waits for the shell to send data containing the given string.

  • #send_and_wait - sends a string and waits for a response containing a specified pattern.

  • #expect - waits for a number of seconds until a pattern is matched by the channel output.

Examples:

ch = get_some_ssh_channel_from_somewhere
ch.extend(Interactive)
ch.send_and_wait("ls -a", PROMPT)
# > we get the result of the `ls -a` command through the channel (hopefully)

Constant Summary collapse

DEFAULT_TIMEOUT =
15

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from EventMachine::Ssh::Callbacks

#callbacks, #callbacks=, #fire, #on, #on_next

Instance Attribute Details

#line_terminatorObject

@return a string (rn) to append to every command



29
30
31
# File 'lib/em-ssh/connection/channel/interactive.rb', line 29

def line_terminator
  @line_terminator
end

Class Method Details

.extended(channel) ⇒ Object



31
32
33
# File 'lib/em-ssh/connection/channel/interactive.rb', line 31

def self.extended(channel)
  channel.init_interactive_module
end

Instance Method Details

#clear_buffer!Object

Remove any data currently in the buffer.



55
56
57
# File 'lib/em-ssh/connection/channel/interactive.rb', line 55

def clear_buffer!
  @buffer = ''
end

#dump_bufferObject Also known as: dump_buffers

@returns Returns a #to_s object describing the dump of the content of the buffers used by

the methods of the interactive module mixed in the host object.


48
49
50
# File 'lib/em-ssh/connection/channel/interactive.rb', line 48

def dump_buffer
  @buffer.dump
end

#expect(strregex, send_str = nil, opts = {}) ⇒ Shell, String

Wait for a number of seconds until a specified string or regexp is matched by the data returned from the ssh channel. Optionally send a given string first.

If a block is not provided the current Fiber will yield until strregex matches or :timeout is reached.

If a block is provided expect will return.

Examples:

expect a prompt

expect(' ~]$ ')

send a command and wait for a prompt

expect(' ~]$ ', '/sbin/ifconfig')

expect a prompt and within 5 seconds

expect(' ~]$ ', :timeout => 5)

send a command and wait up to 10 seconds for a prompt

expect(' ~]$ ', '/etc/sysconfig/openvpn restart', :timeout => 10)

Parameters:

  • strregex (String, Regexp)

    to match against

  • send_str (String) (defaults to: nil)

    the data to send before waiting

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :timeout (Fixnum) — default: @timeout

    number of seconds to wait when there is no activity

Returns:

  • (Shell, String)

    all data received up to an including strregex if a block is not provided. the Shell if a block is provided



81
82
83
84
85
86
87
88
89
90
91
# File 'lib/em-ssh/connection/channel/interactive.rb', line 81

def expect(strregex, send_str = nil, opts = {})
  send_str, opts = nil, send_str if send_str.is_a?(Hash)
  if block_given?
    Fiber.new {
      yield send_str ? send_and_wait(send_str, strregex, opts) : wait_for(strregex, opts)
    }.resume
    self
  else
    send_str ? send_and_wait(send_str, strregex, opts) : wait_for(strregex, opts)
  end
end

#init_interactive_moduleObject

When this module extends an object this method is automatically called (via self#extended). In other cases (include, prepend?), you need to call this method manually before use of the channel.



37
38
39
40
41
42
43
44
# File 'lib/em-ssh/connection/channel/interactive.rb', line 37

def init_interactive_module
  @buffer = ''
  @line_terminator = "\n"
  on_data do |ch, data|
    @buffer += data
    fire(:data, data)
  end
end

#send_and_wait(send_str, wait_str = nil, opts = {}) ⇒ String

Send a string to the server and wait for a response containing a specified String or Regex.

Parameters:

  • send_str (String)

Returns:

  • (String)

    all data in the buffer including the wait_str if it was found



96
97
98
99
# File 'lib/em-ssh/connection/channel/interactive.rb', line 96

def send_and_wait(send_str, wait_str = nil, opts = {})
  send_data(send_str, true)
  return wait_for(wait_str, opts)
end

#send_data(d, send_newline = false) ⇒ Object

Send data to the ssh server shell. You generally don’t need to call this.

Parameters:

  • d (String)

    the data to send encoded as a string

  • send_newline (Boolean) (defaults to: false)

    appends a newline terminator to the data (defaults: false).

See Also:



106
107
108
109
110
111
112
# File 'lib/em-ssh/connection/channel/interactive.rb', line 106

def send_data(d, send_newline=false)
  if send_newline
    super("#{d}#{@line_terminator}")
  else
    super("#{d}")
  end
end

#wait_for(strregex, opts = { }) ⇒ String

Wait for the shell to send data containing the given string.

Parameters:

  • strregex (String, Regexp)

    a string or regex to match the console output against.

  • opts (Hash) (defaults to: { })

Options Hash (opts):

  • :timeout (Fixnum) — default: Session::TIMEOUT

    the maximum number of seconds to wait

Returns:

  • (String)

    the contents of the buffer or a TimeoutError

Raises:

  • Disconnected

  • ClosedChannel

  • TimeoutError



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
# File 'lib/em-ssh/connection/channel/interactive.rb', line 122

def wait_for(strregex, opts = { })
  ###
  log = opts[:log] || NullLogger.new
  timeout_value = opts[:timeout].is_a?(Fixnum) ? opts[:timeout] : DEFAULT_TIMEOUT
  ###
  log.debug("wait_for(#{strregex.inspect}, :timeout => #{timeout_value})")
  opts          = { :timeout => timeout_value }.merge(opts)
  found         = nil
  f             = Fiber.current
  trace         = caller
  timer         = nil
  data_callback = nil
  matched       = false
  started       = Time.new

  timeout_proc = proc do
    data_callback && data_callback.cancel
    f.resume(TimeoutError.new("#{@host}: inactivity timeout (#{opts[:timeout]}) while waiting for #{strregex.inspect}; received: #{buffer.inspect}; waited total: #{Time.new - started}"))
  end

  data_callback = on(:data) do
    timer && timer.cancel
    if matched
      log.warn("data_callback invoked when already matched")
      next
    end
    if (matched = buffer.match(strregex))
      log.debug("matched #{strregex.inspect} on #{buffer.inspect}")
      data_callback.cancel
      @buffer = matched.post_match
      f.resume(matched.pre_match + matched.to_s)
    else
      timer = EM::Timer.new(opts[:timeout], &timeout_proc)
    end
  end

  # Check against current buffer
  EM::next_tick { data_callback.call() if buffer.length > 0 }

  timer = EM::Timer.new(opts[:timeout], &timeout_proc)
  Fiber.yield.tap do |res|
    if res.is_a?(Exception)
      res.set_backtrace(Array(res.backtrace) + trace)
      raise res
    end
    yield(res) if block_given?
  end
end