lib/rye/box.rb in rye-0.7.2 vs lib/rye/box.rb in rye-0.7.3
- old
+ new
@@ -34,12 +34,14 @@
def safe; @rye_safe; end
def user; (@rye_opts || {})[:user]; end
def host=(val); @rye_host = val; end
def opts=(val); @rye_opts = val; end
- def safe=(val); @rye_safe = val; end
-
+
+ def enable_safe_mode; @rye_safe = true; end
+ def disable_safe_mode; @rye_safe = false; end
+
# The most recent value from Box.cd or Box.[]
def current_working_directory; @rye_current_working_directory; end
# The most recent valud for umask (or 0022)
def current_umask; @rye_current_umask; end
@@ -98,11 +100,11 @@
@rye_debug = STDERR if @rye_debug == true
@rye_error = STDERR if @rye_error == true
@rye_info = STDOUT if @rye_info == true
@rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
- @rye_opts[:paranoid] = true unless @rye_opts[:safe] == false # See Net::SSH.start
+ @rye_opts[:paranoid] = true unless @rye_safe == false # See Net::SSH.start
# Add the given private keys to the keychain that will be used for @rye_host
add_keys(@rye_opts[:keys])
# We don't want Net::SSH to handle the keypairs. This may change
@@ -167,61 +169,10 @@
@rye_current_umask = val
self
end
- # Open an SSH session with +@rye_host+. This called automatically
- # when you the first comamnd is run if it's not already connected.
- # Raises a Rye::NoHost exception if +@rye_host+ is not specified.
- # Will attempt a password login up to 3 times if the initial
- # authentication fails.
- # * +reconnect+ Disconnect first if already connected. The default
- # is true. When set to false, connect will do nothing if already
- # connected.
- def connect(reconnect=true)
- raise Rye::NoHost unless @rye_host
- return if @rye_ssh && !reconnect
- disconnect if @rye_ssh
- debug "Opening connection to #{@rye_host} as #{@rye_opts[:user]}"
- highline = HighLine.new # Used for password prompt
- retried = 0
-
- begin
- @rye_ssh = Net::SSH.start(@rye_host, @rye_opts[:user], @rye_opts || {})
- rescue Net::SSH::HostKeyMismatch => ex
- STDERR.puts ex.message
- STDERR.puts "NOTE: EC2 instances generate new SSH keys on first boot."
- print "\a" if @rye_info # Ring the bell
- if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i)
- @rye_opts[:paranoid] = false
- retry
- else
- raise Net::SSH::HostKeyMismatch
- end
- rescue Net::SSH::AuthenticationFailed => ex
- print "\a" if retried == 0 && @rye_info # Ring the bell once
- retried += 1
- if STDIN.tty? && retried <= 3
- STDERR.puts "Passwordless login failed for #{@rye_opts[:user]}"
- @rye_opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
- @rye_opts[:auth_methods] ||= []
- @rye_opts[:auth_methods] << 'password'
- retry
- else
- raise Net::SSH::AuthenticationFailed
- end
- end
-
- # We add :auth_methods (a Net::SSH joint) to force asking for a
- # password if the initial (key-based) authentication fails. We
- # need to delete the key from @rye_opts otherwise it lingers until
- # the next connection (if we switch_user is called for example).
- @rye_opts.delete :auth_methods if @rye_opts.has_key?(:auth_methods)
-
- self
- end
-
# Reconnect as another user. This is different from su=
# which executes subsequent commands via +su -c COMMAND USER+.
# * +newuser+ The username to reconnect as
#
# NOTE: if there is an open connection, it's disconnected
@@ -507,10 +458,30 @@
#
def batch(*args, &block)
self.instance_exec *args, &block
end
+ # Like batch, except it disables safe mode before executing the block.
+ # After executing the block, safe mode is returned back to whichever
+ # state it was previously in. In other words, this method won't enable
+ # safe mode if it was already disabled.
+ def unsafely(*args, &block)
+ previous_state = @rye_safe
+ disable_safe_mode
+ self.instance_exec *args, &block
+ @rye_safe = previous_state
+ end
+
+ # See unsafely (except in reverse)
+ def safely(*args, &block)
+ previous_state = @rye_safe
+ enable_safe_mode
+ self.instance_exec *args, &block
+ @rye_safe = previous_state
+ end
+
+
# instance_exec for Ruby 1.8 written by Mauricio Fernandez
# http://eigenclass.org/hiki/instance_exec
if RUBY_VERSION =~ /1.8/
module InstanceExecHelper; end
include InstanceExecHelper
@@ -537,10 +508,63 @@
@rye_post_command_hook = block if block
@rye_post_command_hook
end
+
+ # Open an SSH session with +@rye_host+. This called automatically
+ # when you the first comamnd is run if it's not already connected.
+ # Raises a Rye::NoHost exception if +@rye_host+ is not specified.
+ # Will attempt a password login up to 3 times if the initial
+ # authentication fails.
+ # * +reconnect+ Disconnect first if already connected. The default
+ # is true. When set to false, connect will do nothing if already
+ # connected.
+ def connect(reconnect=true)
+ raise Rye::NoHost unless @rye_host
+ return if @rye_ssh && !reconnect
+ disconnect if @rye_ssh
+ debug "Opening connection to #{@rye_host} as #{@rye_opts[:user]}"
+ highline = HighLine.new # Used for password prompt
+ retried = 0
+
+ begin
+ @rye_ssh = Net::SSH.start(@rye_host, @rye_opts[:user], @rye_opts || {})
+ rescue Net::SSH::HostKeyMismatch => ex
+ STDERR.puts ex.message
+ STDERR.puts "NOTE: EC2 instances generate new SSH keys on first boot."
+ print "\a" if @rye_info # Ring the bell
+ if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i)
+ @rye_opts[:paranoid] = false
+ retry
+ else
+ raise Net::SSH::HostKeyMismatch
+ end
+ rescue Net::SSH::AuthenticationFailed => ex
+ print "\a" if retried == 0 && @rye_info # Ring the bell once
+ retried += 1
+ if STDIN.tty? && retried <= 3
+ STDERR.puts "Passwordless login failed for #{@rye_opts[:user]}"
+ @rye_opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
+ @rye_opts[:auth_methods] ||= []
+ @rye_opts[:auth_methods] << 'password'
+ retry
+ else
+ raise Net::SSH::AuthenticationFailed
+ end
+ end
+
+ # We add :auth_methods (a Net::SSH joint) to force asking for a
+ # password if the initial (key-based) authentication fails. We
+ # need to delete the key from @rye_opts otherwise it lingers until
+ # the next connection (if we switch_user is called for example).
+ @rye_opts.delete :auth_methods if @rye_opts.has_key?(:auth_methods)
+
+ self
+ end
+
+
private
def debug(msg="unknown debug msg"); @rye_debug.puts msg if @rye_debug; end
def error(msg="unknown error msg"); @rye_error.puts msg if @rye_error; end
def pinfo(msg="unknown info msg"); @rye_info.print msg if @rye_info; end
@@ -580,11 +604,11 @@
cmd, args = prep_args(*args)
connect if !@rye_ssh || @rye_ssh.closed?
raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
-
+
cmd_clean = Rye.escape(@rye_safe, cmd, args)
cmd_clean = prepend_env(cmd_clean)
# Add the current working directory before the command if supplied.
# The command will otherwise run in the user's home directory.
@@ -658,41 +682,50 @@
[cmd, args]
end
# Executes +command+ via SSH
# Returns an Array with 4 elements: [stdout, stderr, exit code, exit signal]
+ # NOTE: This method needs to be replaced to fully support interactive
+ # commands. This implementation is weird because it's getting just STDOUT and
+ # STDERR responses (check value of "type"). on_data and on_extended_data method
+ # hooks are not used. See the following threads for implementation ideas:
+ #
+ # http://www.ruby-forum.com/topic/141814
+ # http://www.ruby-forum.com/topic/169997
+ #
def net_ssh_exec!(command)
block ||= Proc.new do |channel, type, data|
channel[:stdout] ||= ""
channel[:stderr] ||= ""
channel[:exit_code] ||= 0
channel[:stdout] << data if type == :stdout
- channel[:stderr] << data if type == :stderr
+ if type == :stderr
+ # NOTE: Use sudo to test this since it prompts for a passwords.
+ # Use sudo -K to kill the user's timestamp (ask for a password every time)
+ if data =~ /Password:/
+ ret = Annoy.get_user_input("Password: ", '*')
+ channel.send_data "#{ret}\n"
+ else
+ channel[:stderr] << data
+ end
+ end
channel.on_request("exit-status") do |ch, data|
# Anything greater than 0 is an error
channel[:exit_code] = data.read_long
end
channel.on_request("exit-signal") do |ch, data|
# This should be the POSIX SIGNAL that ended the process
channel[:exit_signal] = data.read_long
end
- # For long-running commands like top, this will print the output.
- # It's cool, but we'd also need to enable STDIN to interact with
- # command.
- #channel.on_data do |ch, data|
- # puts "got stdout: #{data}"
- # #channel.send_data "something for stdin\n"
- #end
- #
- #channel.on_extended_data do |ch, data|
- # #puts "got stdout: #{data}"
- # #channel.send_data "something for stdin\n"
- #end
end
channel = @rye_ssh.exec(command, &block)
+
channel.wait # block until we get a response
+ channel.request_pty do |ch, success|
+ raise "Could not obtain pty (i.e. an interactive ssh session)" if !success
+ end
channel[:exit_code] = 0 if channel[:exit_code] == nil
channel[:exit_code] &&= channel[:exit_code].to_i
channel[:stderr].gsub!(/bash: line \d+:\s+/, '') if channel[:stderr]