Skip to content

Commit

Permalink
Progress for 0.6.0 release
Browse files Browse the repository at this point in the history
* FIXED: handling of Process::Status ($?) in Rye.shell
* CHANGE: The Rye::Rap object now contains the exit code as an integer for
Rye.shell and Rye::Box.run_command (SSH) commands.
* ADDED: Rye::Box.umask= (a similar work around as cd / [])
* ADDED: Rye::Box.file_exists?
* CHANGE: rm and kill are available in Rye::Cmd by default
* CHANGE: Rye::Box.authorize_keys renamed Rye::Box.authorize_keys_remote
* CHANGE: Removed Rye::Box.cd alias to [] for directory change
  • Loading branch information
delano committed Apr 27, 2009
1 parent a9d4922 commit 20a08aa
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 43 deletions.
19 changes: 15 additions & 4 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ TODO
* Fingerprints: ssh-keygen -l -f id_rsa_repos.pub


#### 0.6.0 (2009-04-??) #############################

* FIXED: handling of Process::Status ($?) in Rye.shell
* CHANGE: The Rye::Rap object now contains the exit code as an integer for
Rye.shell and Rye::Box.run_command (SSH) commands.
* ADDED: Rye::Box.umask= (a similar work around as cd / [])
* ADDED: Rye::Box.file_exists?
* CHANGE: rm and kill are available in Rye::Cmd by default
* CHANGE: Rye::Box.authorize_keys renamed Rye::Box.authorize_keys_remote
* CHANGE: Removed Rye::Box.cd alias to [] for directory change


#### 0.5.4 (2009-04-22) #############################

* FIXED: Sys is now returning environment paths and home path in JRuby.
Expand All @@ -19,7 +31,6 @@ TODO
* ADDED: Rye::Box.connect now rescues HostKeyMismatch exceptions and
prompts for a response.


#### 0.5.2 (2009-04-19) #############################

* FIXED: authorize-local command attempted to connect via SSH before authorizing.
Expand All @@ -33,12 +44,12 @@ TODO
* FIXED: Method errors in JRuby
* FIXED: Bug in Rye::Set.add_boxes pushing nils into the list of boxes


#### 0.4.3 (2009-04-14) #############################

* ADDED: Rye::Box.missing_method to handle non existent commands
* FIXED: All Rye::Cmd command methods accept *args to make calling consistent.


#### 0.4.2 (2009-04-13) #############################

* ADDED: More helpful debug output
Expand All @@ -51,7 +62,7 @@ TODO

#### 0.4.1 (2009-04-06) #############################

* FIXED: Rye::Box.authorize_keys was not disabling safe mode properly
* FIXED: Rye::Box.authorize_keys_remote was not disabling safe mode properly
* ADDED: "rye authorize" now specifically enforces the auth method order
* FIXED: Disabled debug mode.

Expand Down Expand Up @@ -93,12 +104,12 @@ TODO
* FIXED: Rye::Box.method_missing Symbol/String ambiguity
* ADDED: Mucho more rdocs and examples.


#### 0.2 (2009-04-04) ###############################

* FIXED: ssh-agent shutdown wasn't deleting the SSH tmp directory
* ADDED: Now with more rdocs!


#### 0.1 (2009-04-03) ###############################

Initial public release
Expand Down
2 changes: 1 addition & 1 deletion bin/rye
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ command :authorize do |obj|

puts "Authorizing #{rbox.opts[:user]}@#{hostname}"
rbox = Rye::Box.new(hostname, opts).connect
puts "Added public keys for: ", rbox.authorize_keys
puts "Added public keys for: ", rbox.authorize_keys_remote
puts "Now try: " << "ssh #{rbox.opts[:user]}@#{hostname}"

end
Expand Down
2 changes: 1 addition & 1 deletion bin/try
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ puts %Q(
#)

rbox = Rye::Box.new('localhost', :safe => false)

w w
# Rye follows the standard convention of taking exception to a non-zero
# exit code by raising a Rye::CommandError. In this case, rye9000.test
# is not found by the ls command.
Expand Down
2 changes: 1 addition & 1 deletion lib/rye.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def shell(cmd, *args)
rap = Rye::Rap.new(self)
rap.add_stdout(stdout || '')
rap.add_stderr(stderr || '')
rap.exit_code = $?
rap.add_exit_code($?)
rap
end

Expand Down
140 changes: 108 additions & 32 deletions lib/rye/box.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ module Rye
# rbox.hostname # => localhost
# rbox.uname(:a) # => Darwin vanya 9.6.0 ...
#
#--
# * When anything confusing happens, enable debug in initialize
# by passing :debug => STDERR. This will output Rye debug info
# as well as Net::SSH info. This is VERY helpful for figuring
# out why some command is hanging or otherwise acting weird.
# * If a remote command is hanging, it's probably because a
# Net::SSH channel is waiting on_extended_data (a prompt).
#++
class Box
include Rye::Cmd

# An instance of Net::SSH::Connection::Session
attr_reader :ssh

attr_reader :info
attr_reader :debug
attr_reader :error

Expand All @@ -35,6 +44,8 @@ class Box

# The most recent value from Box.cd or Box.[]
attr_reader :current_working_directory
# The most recent valud for umask (or 0022)
attr_reader :current_umask

# * +host+ The hostname to connect to. The default is localhost.
# * +opts+ a hash of optional arguments.
Expand All @@ -45,6 +56,7 @@ class Box
# * :safe => should Rye be safe? Default: true
# * :keys => one or more private key file paths (passwordless login)
# * :password => the user's password (ignored if there's a valid private key)
# * :info => an IO object to print Rye::Box command info to. Default: nil
# * :debug => an IO object to print Rye::Box debugging info to. Default: nil
# * :error => an IO object to print Rye::Box errors to. Default: STDERR
#
Expand All @@ -59,6 +71,7 @@ def initialize(host='localhost', opts={})
:safe => true,
:port => 22,
:keys => [],
:info => nil,
:debug => nil,
:error => STDERR,
}.merge(opts)
Expand All @@ -77,6 +90,15 @@ def initialize(host='localhost', opts={})
@safe = @opts.delete(:safe)
@debug = @opts.delete(:debug)
@error = @opts.delete(:error)
@info = @opts.delete(:info)

@debug = STDERR if @debug == true
@error = STDERR if @error == true
@info = STDOUT if @info == true

if @debug
@opts[:logger] = Logger.new(@debug)
end

debug @opts.inspect

Expand All @@ -86,7 +108,9 @@ def initialize(host='localhost', opts={})
# but for we're letting ssh-agent do it.
#@opts.delete(:keys)


# From: capistrano/lib/capistrano/cli.rb
STDOUT.sync = true # so that Net::SSH prompts show up

debug "ssh-agent info: #{Rye.sshagent_info.inspect}"

end
Expand Down Expand Up @@ -123,26 +147,42 @@ def [](key=nil)
@current_working_directory = key
self
end
# alias :cd :'[]' # fix for jruby
def cd(key=nil);
@current_working_directory = key


# Change the current umask (sort of -- works the same way as cd)
# The default umask is 0022
def umask=(val='0022')
@current_umask = val
self
end


# Execute subsequent commands via +su -c COMMAND user+
# * +user+ is the the remote user name to run as. Set to nil to remove.
def su=(user)
@current_su = user
self
end

# Open an SSH session with +@host+. This called automatically
# when you the first comamnd is run if it's not already connected.
# Raises a Rye::NoHost exception if +@host+ is not specified.
# Will attempt a password login up to 3 times if the initial
# authentication fails.
def connect
# * +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 @host
return if @ssh && !reconnect
disconnect if @ssh
debug "Opening connection to #{@host} as #{@opts[:user]}"
highline = HighLine.new # Used for password prompt
retried = 0

begin
@ssh = Net::SSH.start(@host, @opts[:user], @opts || {})
#puts @ssh.sync
#@ssh = @ssh.shell.sync
rescue Net::SSH::HostKeyMismatch => ex
STDERR.puts ex.message
STDERR.puts "NOTE: EC2 instances generate new SSH keys on first boot."
Expand Down Expand Up @@ -175,16 +215,8 @@ def connect
self
end

# Close the SSH session with +@host+. This is called
# automatically at exit if the connection is open.
def disconnect
return unless @ssh && !@ssh.closed?
@ssh.loop(0.1) { @ssh.busy? }
debug "Closing connection to #{@ssh.host}"
@ssh.close
end

# Reconnect as another user
# 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
Expand All @@ -197,6 +229,25 @@ def switch_user(newuser)
connect
end


# Close the SSH session with +@host+. This is called
# automatically at exit if the connection is open.
def disconnect
return unless @ssh && !@ssh.closed?
@ssh.loop(0.1) { @ssh.busy? }
debug "Closing connection to #{@ssh.host}"
@ssh.close
end


# Does a remote path exist?
def file_exists?(path)
ret = self.ls(path)
# "ls" returns a 0 exit code regardless of success
# For some reason the same goes for "test".
ret.stderr.empty?
end

# Open an interactive SSH session. This only works if STDIN.tty?
# returns true. Otherwise it returns the SSH command that would
# have been run. This requires the SSH command-line executable (ssh).
Expand Down Expand Up @@ -251,9 +302,10 @@ def to_s
end

def inspect
%q{#<%s:%s cwd=%s env=%s safe=%s opts=%s>} %
%q{#<%s:%s cwd=%s umask=%s su=%s env=%s safe=%s opts=%s>} %
[self.class.to_s, self.host,
@current_working_directory, (@current_environment_variables || '').inspect,
@current_working_directory, @current_umask,
@current_su, (@current_environment_variables || '').inspect,
self.safe, self.opts.inspect]
end

Expand All @@ -273,20 +325,24 @@ def host_key
# this box into ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2.
# Returns an Array of the private keys files used to generate the public keys.
#
# NOTE: authorize_keys disables safe-mode for this box while it runs
# NOTE: authorize_keys_remote disables safe-mode for this box while it runs
# which will hit you funky style if your using a single instance
# of Rye::Box in a multithreaded situation.
#
def authorize_keys
def authorize_keys_remote(other_user=nil)
added_keys = []
@safe= false
@safe = false
home_path = "$HOME/.."

Rye.keys.each do |key|
path = key[2]
debug "# Public key for #{path}"
k = Rye::Key.from_file(path).public_key.to_ssh2
self.su = other_user # does nothing if nil
self.mkdir(:p, :m, '700', '$HOME/.ssh') # Silently create dir if it doesn't exist
self.echo("'#{k}' >> $HOME/.ssh/authorized_keys")
self.echo("'#{k}' >> $HOME/.ssh/authorized_keys2")
self.su = nil # disable su if set
self.chmod('-R', '0600', '$HOME/.ssh/authorized_keys*')
added_keys << path
end
Expand All @@ -296,7 +352,7 @@ def authorize_keys

# Authorize the current user to login to the local machine via
# SSH without a password. This is the same functionality as
# authorize_keys except run with local shell commands.
# authorize_keys_remote except run with local shell commands.
def authorize_keys_local
added_keys = []
Rye.keys.each do |key|
Expand All @@ -323,10 +379,9 @@ def preview_command(*args)

private


def debug(msg="unknown debug msg"); @debug.puts msg if @debug; end
def error(msg="unknown error msg"); @error.puts msg if @error; end

def info(msg="unknown info msg"); @info.puts msg if @info; end

# Add the current environment variables to the beginning of +cmd+
def prepend_env(cmd)
Expand Down Expand Up @@ -367,17 +422,33 @@ def run_command(*args)

cmd_clean = Rye.escape(@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.
if @current_working_directory
cwd = Rye.escape(@safe, 'cd', @current_working_directory)
cmd_clean = [cwd, cmd_clean].join(' && ')
end

# ditto (same explanation as cwd)
if @current_umask
cwd = Rye.escape(@safe, 'umask', @current_umask)
cmd_clean = [cwd, cmd_clean].join(' && ')
end

if @current_su
cmd_clean = "su -c '%s'" % cmd_clean.tr("'", "''")
cmd_clean = Rye.escape(@safe, cmd_clean, @current_su)
end

info "COMMAND: #{cmd_clean}"
debug "Executing: %s" % cmd_clean
stdout, stderr, ecode, esignal = net_ssh_exec! cmd_clean

stdout, stderr, ecode, esignal = net_ssh_exec!(cmd_clean)
rap = Rye::Rap.new(self)
rap.add_stdout(stdout || '')
rap.add_stderr(stderr || '')
rap.exit_code = ecode
rap.add_exit_code(ecode)
rap.exit_signal = esignal
rap.cmd = cmd

Expand All @@ -387,8 +458,6 @@ def run_command(*args)
end
alias :cmd :run_command



# Takes a list of arguments appropriate for run_command or
# preview_command and returns: [cmd, args]
def prep_args(*args)
Expand All @@ -408,9 +477,11 @@ def prep_args(*args)
# Executes +command+ via SSH
# Returns an Array with 4 elements: [stdout, stderr, exit code, exit signal]
def net_ssh_exec!(command)

block ||= Proc.new do |channel, type, data|
channel[:stdout] ||= ""
channel[:stderr] ||= ""
channel[:exit_code] ||= -1
channel[:stdout] << data if type == :stdout
channel[:stderr] << data if type == :stderr
channel.on_request("exit-status") do |ch, data|
Expand All @@ -424,10 +495,15 @@ def net_ssh_exec!(command)
# 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_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 = @ssh.exec(command, &block)
Expand Down
Loading

0 comments on commit 20a08aa

Please sign in to comment.