rye 0.8.19 → 0.9.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.
Files changed (10) hide show
  1. data/CHANGES.txt +17 -1
  2. data/README.rdoc +2 -29
  3. data/bin/try +42 -13
  4. data/lib/rye/box.rb +318 -224
  5. data/lib/rye/cmd.rb +156 -78
  6. data/lib/rye/rap.rb +10 -6
  7. data/lib/rye.rb +32 -5
  8. data/rye.gemspec +4 -7
  9. metadata +68 -49
  10. data/bin/rye +0 -147
data/lib/rye/box.rb CHANGED
@@ -1,6 +1,8 @@
1
-
1
+ require 'annoy'
2
+ require 'readline'
2
3
 
3
4
  module Rye
5
+ DEBUG = false unless defined?(Rye::DEBUG)
4
6
 
5
7
  # = Rye::Box
6
8
  #
@@ -29,12 +31,18 @@ module Rye
29
31
  class Box
30
32
  include Rye::Cmd
31
33
 
34
+ attr_accessor :rye_shell
35
+ attr_accessor :rye_pty
36
+
32
37
  def host; @rye_host; end
33
38
  def opts; @rye_opts; end
34
39
  def safe; @rye_safe; end
35
40
  def user; @rye_user; end
36
41
  def root?; user.to_s == "root" end
37
42
 
43
+ def templates; @rye_templates; end
44
+ def templates?; !@rye_templates.nil?; end
45
+
38
46
  def enable_sudo; @rye_sudo = true; end
39
47
  def disable_sudo; @rye_sudo = false; end
40
48
  def sudo?; @rye_sudo == true end
@@ -64,13 +72,14 @@ module Rye
64
72
  # The most recent valud for umask (or 0022)
65
73
  def current_umask; @rye_current_umask; end
66
74
 
67
- def info; @rye_info; end
68
- def debug; @rye_debug; end
69
- def error; @rye_error; end
75
+ def info?; !@rye_info.nil?; end
76
+ def debug?; !@rye_debug.nil?; end
77
+ def error?; !@rye_error.nil?; end
70
78
 
71
79
  def ostype=(val); @rye_ostype = val; end
72
80
  def impltype=(val); @rye_impltype = val; end
73
81
  def pre_command_hook=(val); @rye_pre_command_hook = val; end
82
+ def stdout_hook=(val); @rye_stdout_hook = val; end
74
83
  def post_command_hook=(val); @rye_post_command_hook = val; end
75
84
  # A Hash. The keys are exception classes, the values are Procs to execute
76
85
  def exception_hook=(val); @rye_exception_hook = val; end
@@ -89,6 +98,7 @@ module Rye
89
98
  # * :error => an IO object to print Rye::Box errors to. Default: STDERR
90
99
  # * :getenv => pre-fetch +host+ environment variables? (default: true)
91
100
  # * :password => the user's password (ignored if there's a valid private key)
101
+ # * :templates => the template engine to use for uploaded files. One of: :erb (default)
92
102
  # * :sudo => Run all commands via sudo (default: false)
93
103
  #
94
104
  # NOTE: +opts+ can also contain any parameter supported by
@@ -109,11 +119,12 @@ module Rye
109
119
  @rye_opts = {
110
120
  :safe => true,
111
121
  :port => ssh_opts[:port],
112
- :keys => [],
122
+ :keys => Rye.keys,
113
123
  :info => nil,
114
124
  :debug => nil,
115
125
  :error => STDERR,
116
126
  :getenv => true,
127
+ :templates => :erb,
117
128
  :quiet => false
118
129
  }.merge(opts)
119
130
 
@@ -128,16 +139,24 @@ module Rye
128
139
  @rye_getenv = {} if @rye_opts.delete(:getenv) # Enable getenv with a hash
129
140
  @rye_ostype, @rye_impltype = @rye_opts.delete(:ostype), @rye_opts.delete(:impltype)
130
141
  @rye_quiet, @rye_sudo = @rye_opts.delete(:quiet), @rye_opts.delete(:sudo)
142
+ @rye_templates = @rye_opts.delete(:templates)
131
143
 
132
- # Just in case someone sends a true value rather than IO object
133
- @rye_debug = STDERR if @rye_debug == true
134
- @rye_error = STDERR if @rye_error == true
135
- @rye_info = STDOUT if @rye_info == true
144
+ # Store the state of the terminal
145
+ @rye_stty_save = `stty -g`.chomp rescue nil
146
+
147
+ unless @rye_templates.nil?
148
+ require @rye_templates.to_s # should be :erb
149
+ end
136
150
 
137
151
  @rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
138
152
  @rye_opts[:paranoid] = true unless @rye_safe == false # See Net::SSH.start
139
153
  @rye_opts[:keys] = [@rye_opts[:keys]].flatten.compact
140
154
 
155
+ # Just in case someone sends a true value rather than IO object
156
+ @rye_debug = STDERR if @rye_debug == true || DEBUG
157
+ @rye_error = STDERR if @rye_error == true
158
+ @rye_info = STDOUT if @rye_info == true
159
+
141
160
  # Add the given private keys to the keychain that will be used for @rye_host
142
161
  add_keys(@rye_opts[:keys])
143
162
 
@@ -223,12 +242,23 @@ module Rye
223
242
  end
224
243
 
225
244
 
226
- # Open an interactive SSH session. This only works if STDIN.tty?
227
- # returns true. Otherwise it returns the SSH command that would
228
- # have been run. This requires the SSH command-line executable (ssh).
229
- # * +run+ when set to false, it will return the SSH command as a String
230
- # and not open an SSH session.
245
+ # If STDIN.tty? is true (i.e. if we're connected to a terminal
246
+ # with a human at the helm), this will open an SSH connection
247
+ # via the regular SSH command (via a call to system). This
248
+ # requires the SSH command-line executable (ssh).
249
+ #
250
+ # If STDIN.tty? is false or +run+ is false, this will return
251
+ # the SSH command (a String) that would have been run.
252
+ #
253
+ # NOTE: As of Rye 0.9 you can run interactive sessions with
254
+ # rye by calling any shell method without arguments.
255
+ #
256
+ # e.g.
257
+ #
258
+ # rbox = Rye::Box.new 'somemachine'
259
+ # rbox.bash
231
260
  #
261
+ # TODO: refactor to use net_ssh_exec! in 0.9
232
262
  def interactive_ssh(run=true)
233
263
  debug "interactive_ssh with keys: #{Rye.keys.inspect}"
234
264
  run = false unless STDIN.tty?
@@ -249,11 +279,11 @@ module Rye
249
279
  additional_keys = [additional_keys].flatten.compact || []
250
280
  return if additional_keys.empty?
251
281
  ret = Rye.add_keys(additional_keys)
252
- if ret.is_a?(Rye::Rap)
253
- debug "ssh-add exit_code: #{ret.exit_code}"
254
- debug "ssh-add stdout: #{ret.stdout}"
255
- debug "ssh-add stderr: #{ret.stderr}"
256
- end
282
+ #if ret.is_a?(Rye::Rap)
283
+ # debug "ssh-add exit_status: #{ret.exit_status}"
284
+ # debug "ssh-add stdout: #{ret.stdout}"
285
+ # debug "ssh-add stderr: #{ret.stderr}"
286
+ #end
257
287
  self # MUST RETURN self
258
288
  end
259
289
  alias :add_key :add_keys
@@ -379,110 +409,6 @@ module Rye
379
409
  @rye_guessed_homes[this_user] = "#{user_defaults['HOME']}/#{this_user}"
380
410
  end
381
411
 
382
- # Copy the local public keys (as specified by Rye.keys) to
383
- # this box into ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2.
384
- # Returns a Rye::Rap object. The private keys files used to generate
385
- # the public keys are contained in stdout.
386
- # Raises a Rye::ComandError if the home directory doesn't exit.
387
- # NOTE: authorize_keys_remote disables safe-mode for this box while it runs
388
- # which will hit you funky style if your using a single instance
389
- # of Rye::Box in a multithreaded situation.
390
- #
391
- def authorize_keys_remote(other_user=nil)
392
- if other_user.nil?
393
- this_user = opts[:user]
394
- homedir = self.quietly { pwd }.first
395
- else
396
- this_user = other_user
397
- # The homedir path is important b/c this is where we're going to
398
- # look for the .ssh directory. That's where auth love is stored.
399
- homedir = self.guess_user_home(this_user)
400
- end
401
-
402
- added_keys = []
403
- rap = Rye::Rap.new(self)
404
-
405
- prevdir = self.current_working_directory
406
-
407
- unless self.file_exists?(homedir)
408
- rap.add_exit_code(1)
409
- rap.add_stderr("Path does not exist: #{homedir}")
410
- raise Rye::CommandError.new(rap)
411
- end
412
-
413
- # Let's go into the user's home directory that we now know exists.
414
- self.cd homedir
415
-
416
- files = ['.ssh/authorized_keys', '.ssh/authorized_keys2', '.ssh/identity']
417
- files.each do |akey_path|
418
- if self.file_exists?(akey_path)
419
- # TODO: Make Rye::Cmd.incremental_backup
420
- self.cp(akey_path, "#{akey_path}-previous")
421
- authorized_keys = self.file_download("#{homedir}/#{akey_path}")
422
- end
423
- authorized_keys ||= StringIO.new
424
-
425
- Rye.keys.each do |path|
426
-
427
- info "# Adding public key for #{path}"
428
- k = Rye::Key.from_file(path).public_key.to_ssh2
429
- authorized_keys.puts k
430
- end
431
-
432
- # Remove duplicate authorized keys
433
- authorized_keys.rewind
434
- uniqlines = authorized_keys.readlines.uniq.join
435
- authorized_keys = StringIO.new(uniqlines)
436
- # We need to rewind so that all of the StringIO object is uploaded
437
- authorized_keys.rewind
438
-
439
- full_path = "#{homedir}/#{akey_path}"
440
- temp_path = "/tmp/rye-#{user}-#{File.basename(akey_path)}"
441
-
442
- sudo do
443
- mkdir :p, :m, '700', File.dirname(akey_path)
444
- file_upload authorized_keys, temp_path
445
- mv temp_path, full_path
446
- chmod '0600', akey_path
447
- chown :R, this_user.to_s, File.dirname(akey_path)
448
- end
449
- end
450
-
451
- # And let's return to the directory we came from.
452
- self.cd prevdir
453
-
454
- rap.add_exit_code(0)
455
- rap
456
- end
457
- require 'fileutils'
458
- # Authorize the current user to login to the local machine via
459
- # SSH without a password. This is the same functionality as
460
- # authorize_keys_remote except run with local shell commands.
461
- def authorize_keys_local
462
- added_keys = []
463
- ssh_dir = File.join(Rye.sysinfo.home, '.ssh')
464
- Rye.keys.each do |path|
465
- debug "# Public key for #{path}"
466
- k = Rye::Key.from_file(path).public_key.to_ssh2
467
- FileUtils.mkdir ssh_dir unless File.exists? ssh_dir
468
-
469
- authkeys_file = File.join(ssh_dir, 'authorized_keys')
470
-
471
- debug "Writing to #{authkeys_file}"
472
- File.open(authkeys_file, 'a') {|f| f.write("#{$/}#{k}") }
473
- File.open("#{authkeys_file}2", 'a') {|f| f.write("#{$/}#{k}") }
474
-
475
- unless Rye.sysinfo.os == :windows
476
- Rye.shell(:chmod, '700', ssh_dir)
477
- Rye.shell(:chmod, '0600', authkeys_file)
478
- Rye.shell(:chmod, '0600', "#{authkeys_file}2")
479
- end
480
-
481
- added_keys << path
482
- end
483
- added_keys
484
- end
485
-
486
412
  # A handler for undefined commands.
487
413
  # Raises Rye::CommandNotFound exception.
488
414
  def method_missing(cmd, *args, &block)
@@ -518,6 +444,17 @@ module Rye
518
444
  @rye_pre_command_hook
519
445
  end
520
446
 
447
+ # Supply a block to be called every time a command receives STDOUT data.
448
+ #
449
+ # e.g.
450
+ # rbox.stdout_hook do |content|
451
+ # ...
452
+ # end
453
+ def stdout_hook(&block)
454
+ @rye_stdout_hook = block if block
455
+ @rye_stdout_hook
456
+ end
457
+
521
458
  # Supply a block to be called whenever there's an Exception. It's called
522
459
  # with 1 argument: the exception class. If the exception block returns
523
460
  # :retry, the command will be executed again.
@@ -559,7 +496,7 @@ module Rye
559
496
  # Returns the return value of the block.
560
497
  #
561
498
  def batch(*args, &block)
562
- self.instance_exec *args, &block
499
+ self.instance_exec(*args, &block)
563
500
  end
564
501
 
565
502
  # Like batch, except it disables safe mode before executing the block.
@@ -573,6 +510,7 @@ module Rye
573
510
  @rye_safe = previous_state
574
511
  ret
575
512
  end
513
+ alias_method :wildly, :unsafely
576
514
 
577
515
  # See unsafely (except in reverse)
578
516
  def safely(*args, &block)
@@ -607,7 +545,7 @@ module Rye
607
545
  # command.
608
546
  def sudo(*args, &block)
609
547
  if block.nil?
610
- __allow('sudo', args);
548
+ run_command('sudo', args);
611
549
  else
612
550
  previous_state = @rye_sudo
613
551
  enable_sudo
@@ -703,15 +641,19 @@ module Rye
703
641
  def disconnect
704
642
  return unless @rye_ssh && !@rye_ssh.closed?
705
643
  begin
706
- Timeout::timeout(10) do
707
- @rye_ssh.loop(0.3) { @rye_ssh.busy?; }
644
+ if @rye_ssh.busy?;
645
+ info "Is something still running? (ctrl-C to exit)"
646
+ Timeout::timeout(10) do
647
+ @rye_ssh.loop(0.3) { @rye_ssh.busy?; }
648
+ end
708
649
  end
650
+ debug "Closing connection to #{@rye_ssh.host}"
651
+ @rye_ssh.close
709
652
  rescue SystemCallError, Timeout::Error => ex
710
- error "Disconnect timeout (was something still running?)"
653
+ error "Disconnect timeout"
654
+ rescue Interrupt
655
+ debug "Exiting..."
711
656
  end
712
-
713
- debug "Closing connection to #{@rye_ssh.host}"
714
- @rye_ssh.close
715
657
  end
716
658
 
717
659
 
@@ -751,11 +693,13 @@ module Rye
751
693
  # This method will try to connect to the host automatically
752
694
  # but if it fails it will raise a Rye::NotConnected exception.
753
695
  #
754
- def run_command(*args)
755
- debug "run_command with keys: #{Rye.keys.inspect}"
696
+ def run_command(*args, &blk)
697
+ debug "run_command"
756
698
 
757
699
  cmd, args = prep_args(*args)
758
700
 
701
+ #p [:run_command, cmd, blk.nil?]
702
+
759
703
  connect if !@rye_ssh || @rye_ssh.closed?
760
704
  raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
761
705
 
@@ -769,7 +713,7 @@ module Rye
769
713
  # The command will otherwise run in the user's home directory.
770
714
  if @rye_current_working_directory
771
715
  cwd = Rye.escape(@rye_safe, 'cd', @rye_current_working_directory)
772
- cmd_internal = [cwd, cmd_internal].join(' && ')
716
+ cmd_internal = '(%s; %s)' % [cwd, cmd_internal]
773
717
  end
774
718
 
775
719
  # ditto (same explanation as cwd)
@@ -797,22 +741,24 @@ module Rye
797
741
  rap = Rye::Rap.new(self)
798
742
  rap.cmd = cmd_clean
799
743
 
800
- stdout, stderr, ecode, esignal = net_ssh_exec!(cmd_internal)
744
+ channel = net_ssh_exec!(cmd_internal, &blk)
801
745
 
802
- rap.add_stdout(stdout || '')
803
- rap.add_stderr(stderr || '')
804
- rap.add_exit_code(ecode)
805
- rap.exit_signal = esignal
746
+ if channel[:exception]
747
+ rap = channel[:exception].rap
748
+ else
749
+ rap.add_stdout(channel[:stdout].read || '')
750
+ rap.add_stderr(channel[:stderr].read || '')
751
+ rap.add_exit_status(channel[:exit_status])
752
+ rap.exit_signal = channel[:exit_signal]
753
+ end
806
754
 
807
- #info "stdout: #{rap.stdout}"
808
- #info "stderr: #{rap.stderr}"
809
- #info "exit_code: #{rap.exit_code}"
755
+ debug "RESULT: %s " % [rap.inspect]
810
756
 
811
757
  # It seems a convention for various commands to return -1
812
- # when something only mildly concerning happens. ls even
813
- # returns -1 for apparently no reason sometimes. In any
814
- # case, the real errors are the ones greater than zero
815
- raise Rye::CommandError.new(rap) if ecode != 0
758
+ # when something only mildly concerning happens. (ls even
759
+ # returns -1 for apparently no reason sometimes). Anyway,
760
+ # the real errors are the ones that are greater than zero.
761
+ raise Rye::Err.new(rap) if rap.exit_status != 0
816
762
 
817
763
  rescue Exception => ex
818
764
  return rap if @rye_quiet
@@ -826,7 +772,14 @@ module Rye
826
772
  retry
827
773
  elsif choice == :skip
828
774
  # do nothing
829
- else
775
+ elsif choice == :interactive && !@rye_shell
776
+ @rye_shell = true
777
+ previous_state = @rye_sudo
778
+ disable_sudo
779
+ bash
780
+ @rye_sudo = previous_state
781
+ @rye_shell = false
782
+ elsif !ex.is_a?(Interrupt)
830
783
  raise ex, ex.message
831
784
  end
832
785
  end
@@ -858,88 +811,233 @@ module Rye
858
811
  [cmd, args]
859
812
  end
860
813
 
861
- # Executes +command+ via SSH
862
- # Returns an Array with 4 elements: [stdout, stderr, exit code, exit signal]
863
- # NOTE: This method needs to be replaced to fully support interactive
864
- # commands. This implementation is weird because it's getting just STDOUT and
865
- # STDERR responses (check value of "type"). on_data and on_extended_data method
866
- # hooks are not used. See the following threads for implementation ideas:
867
- #
868
- # http://www.ruby-forum.com/topic/141814
869
- # http://www.ruby-forum.com/topic/169997
870
- #
871
- def net_ssh_exec!(command)
814
+ def net_ssh_exec!(cmd, &blk)
815
+ debug ":net_ssh_exec #{cmd} (has blk: #{!blk.nil?}; pty: #{@rye_pty}; shell: #{@rye_shell})"
816
+
817
+ pty_opts = { :term => "xterm",
818
+ :chars_wide => 80,
819
+ :chars_high => 24,
820
+ :pixels_wide => 640,
821
+ :pixels_high => 480,
822
+ :modes => {} }
823
+
824
+ channel = @rye_ssh.open_channel do |channel|
825
+ if self.rye_shell && blk.nil?
826
+ channel.request_pty(pty_opts) do |ch,success|
827
+ self.rye_pty = success
828
+ raise Rye::NoPty if !success
829
+ end
830
+ end
831
+ channel.exec(cmd, &create_channel)
832
+ channel[:state] = :start_session
833
+ channel[:block] = blk
834
+ end
835
+
836
+ @rye_channels ||= []
837
+ @rye_channels << channel
838
+
839
+ @rye_ssh.loop(0.1) do
840
+ break if channel.nil? || !channel.active?
841
+ !channel.eof? # otherwise keep returning true
842
+ end
843
+
844
+ channel
845
+ end
846
+
847
+
848
+ def state_wait_for_command(channel)
849
+ debug :wait_for_command
850
+ end
872
851
 
873
- block ||= Proc.new do |channel, type, data, tt|
852
+ def state_start_session(channel)
853
+ debug "#{:start_session} [blk: #{!channel[:block].nil?}] [pty: #{@rye_pty}] [shell: #{@rye_shell}]"
854
+ channel[:state] = nil
855
+ channel[:state] = :run_block if channel[:block]
856
+ channel[:state] = :await_response if @rye_pty
857
+ end
858
+
859
+ def state_await_response(channel)
860
+ debug :await_response
861
+ @await_response_counter ||= 0
862
+ if channel[:stdout].available > 0 || channel[:stderr].available > 0
863
+ channel[:state] = :read_response
864
+ elsif @await_response_counter > 50
865
+ @await_response_counter = 0
866
+ channel[:state] = :await_input
867
+ end
868
+ @await_response_counter += 1
869
+ end
870
+
871
+ def state_read_response(channel)
872
+ debug :read_response
873
+ if channel[:stdout].available > 0 || channel[:stderr].available > 0
874
874
 
875
- channel[:stdout] ||= ""
876
- channel[:stderr] ||= ""
875
+ stdout = channel[:stdout].read if channel[:stdout].available > 0
876
+ stderr = channel[:stderr].read if channel[:stderr].available > 0
877
877
 
878
+ print stdout if stdout
879
+ print stderr if stderr
878
880
 
879
- if type == :stderr
880
- # NOTE: Use sudo to test this since it prompts for a passwords.
881
- # Use sudo -K to kill the user's timestamp (ask for a password every time)
882
- if data =~ /password:/i
883
- ret = Annoy.get_user_input("Password: ", '*')
884
- raise Rye::NoPassword if ret.nil?
885
- channel.send_data "#{ret}\n"
886
- else
887
- channel[:stderr] << data
881
+ if channel[:stack].empty?
882
+ channel[:state] = :await_input
883
+ elsif channel[:stdout].available > 0 || channel[:stderr].available > 0
884
+ channel[:state] = :read_response
885
+ else
886
+ channel[:state] = :send_data
887
+ end
888
+ else
889
+ channel[:state] = :await_response
890
+ end
891
+
892
+ end
893
+
894
+ def state_send_data(channel)
895
+ debug :send_data
896
+ cmd = channel[:stack].shift
897
+ debug "sending #{cmd.inspect}"
898
+ channel[:state] = :await_response
899
+ channel.send_data("#{cmd}\n") unless channel.eof?
900
+ end
901
+
902
+ def state_await_input(channel)
903
+ debug :await_input
904
+ if channel[:stdout].available > 0
905
+ channel[:state] = :read_response
906
+ else
907
+ ret = nil
908
+ if channel[:prompt] && (channel[:prompt] =~ /pass/i)
909
+ ret = Annoy.get_user_input("#{channel[:prompt]} ", echo='*', period=30)
910
+ channel[:prompt] = nil
888
911
  end
912
+ begin
913
+ list = self.commands.sort
889
914
 
890
- # If someone tries to open an interactive ssh session
891
- # through a regular Rye::Box command, Net::SSH will
892
- # return the following error and appear to hang. We
893
- # catch it and raise the appropriate exception.
894
- raise Rye::NoPty if data =~ /Pseudo-terminal will not/
895
- elsif type == :stdout
896
- if data =~ /Select gem to uninstall/i
897
- puts data
898
- ret = Annoy.get_user_input('')
899
- raise "No input given" if ret.nil?
900
- channel.send_data "#{ret}\n"
901
- else
902
- channel[:stdout] << data
915
+ comp = proc { |s|
916
+ # TODO: Something here for files
917
+ list.grep( /^#{Regexp.escape(s)}/ )
918
+ }
919
+
920
+ Readline.completion_append_character = " "
921
+ Readline.completion_proc = comp
922
+
923
+ ret = Readline.readline(channel[:prompt] || '', true)
924
+ #ret = STDIN.gets
925
+
926
+ if ret.nil?
927
+ channel[:state] = :exit
928
+ else
929
+ channel[:stack] << ret.chomp
930
+ channel[:state] = :send_data
931
+ end
932
+ rescue Interrupt => e
933
+ channel[:state] = :exit
903
934
  end
935
+ channel[:prompt] = nil
904
936
  end
905
-
937
+ end
938
+
939
+ def state_ignore_response(channel)
940
+ debug :ignore_response
941
+ @ignore_response_counter ||= 0
942
+ if channel[:stdout].available > 0
943
+ @await_response_counter = 0
944
+ channel[:stdout].read
945
+ channel[:state] = :process
946
+ elsif @ignore_response_counter > 2
947
+ @await_response_counter = 0
948
+ channel[:state] = :process
906
949
  end
907
-
908
- channel = @rye_ssh.exec(command, &block)
909
-
910
- channel.on_request("exit-status") do |ch, data|
911
- # Anything greater than 0 is an error
912
- channel[:exit_code] = data.read_long
950
+ @ignore_response_counter += 1
951
+ end
952
+
953
+ def state_exit(channel)
954
+ debug :exit_state
955
+ channel[:state] = nil
956
+ if rye_shell && (!channel.eof? || !channel.closing?)
957
+ puts
958
+ channel.send_data("exit\n")
959
+ else
960
+ channel.eof!
913
961
  end
914
- channel.on_request("exit-signal") do |ch, data|
915
- # This should be the POSIX SIGNAL that ended the process
916
- channel[:exit_signal] = data.read_long
962
+ end
963
+
964
+ # TODO: implement callback in create_channel Proc
965
+ ##def state_handle_error(channel)
966
+ ## debug :handle_error
967
+ ## channel[:state] = nil
968
+ ## if rye_shell && (!channel.eof? || !channel.closing?)
969
+ ## puts
970
+ ## channel.send_data("exit\n")
971
+ ## else
972
+ ## channel.eof!
973
+ ## end
974
+ ##end
975
+
976
+
977
+ def state_run_block(channel)
978
+ debug :run_block
979
+ channel[:state] = nil
980
+ blk = channel[:block]
981
+ channel[:block] = nil
982
+ begin
983
+ instance_eval &blk
984
+ rescue => ex
985
+ channel[:exception] = ex
917
986
  end
918
-
919
- channel.wait # block until we get a response
920
- channel.request_pty do |ch, success|
921
- raise Rye::NoPty if !success
987
+ channel[:state] = :exit
988
+ end
989
+
990
+ def create_channel()
991
+ Proc.new do |channel,success|
992
+ channel[:stdout ] = Net::SSH::Buffer.new
993
+ channel[:stderr ] = Net::SSH::Buffer.new
994
+ channel[:stack] ||= []
995
+ channel.on_close { |ch|
996
+ channel[:handler] = ":on_close"
997
+ }
998
+ channel.on_data { |ch, data|
999
+ channel[:handler] = ":on_data"
1000
+ @rye_stdout_hook.call(data) if !@rye_pty && !@rye_quiet && @rye_stdout_hook.kind_of?(Proc)
1001
+ if rye_pty && data =~ /password/i
1002
+ channel[:prompt] = data
1003
+ channel[:state] = :await_input
1004
+ else
1005
+ channel[:stdout].append(data)
1006
+ end
1007
+ }
1008
+ channel.on_extended_data { |ch, type, data|
1009
+ channel[:handler] = ":on_extended_data"
1010
+ if rye_pty && data =~ /\Apassword/i
1011
+ channel[:prompt] = data
1012
+ channel[:state] = :await_input
1013
+ else
1014
+ channel[:stderr].append(data)
1015
+ end
1016
+ }
1017
+ channel.on_request("exit-status") { |ch, data|
1018
+ channel[:handler] = ":on_request (exit-status)"
1019
+ channel[:exit_status] = data.read_long
1020
+ }
1021
+ channel.on_request("exit-signal") do |ch, data|
1022
+ channel[:handler] = ":on_request (exit-signal)"
1023
+ # This should be the POSIX SIGNAL that ended the process
1024
+ channel[:exit_signal] = data.read_long
1025
+ end
1026
+ channel.on_process {
1027
+ channel[:handler] = :on_process
1028
+ print channel[:stderr].read if channel[:stderr].available > 0
1029
+ begin
1030
+ send("state_#{channel[:state]}", channel) unless channel[:state].nil?
1031
+ rescue Interrupt
1032
+ debug :on_process_interrupt
1033
+ channel[:state] = :exit
1034
+ end
1035
+ }
922
1036
  end
923
-
924
- ## I'm getting weird behavior with exit codes. Sometimes
925
- ## a command which usually returns an exit code will not
926
- ## return one the next time it's run. The following crap
927
- ## was from the debugging.
928
- ##Kernel.sleep 5
929
- ###channel.close
930
- #channel.eof!
931
- ##p [:active, channel.active?]
932
- ##p [:closing, channel.closing?]
933
- ##p [:eof, channel.eof?]
934
-
935
- channel[:exit_code] ||= 0
936
- channel[:exit_code] &&= channel[:exit_code].to_i
937
- channel[:stderr].gsub!(/bash: line \d+:\s+/, '') if channel[:stderr]
938
-
939
- [channel[:stdout], channel[:stderr], channel[:exit_code], channel[:exit_signal]]
940
1037
  end
941
1038
 
942
1039
 
1040
+
943
1041
  # * +direction+ is one of :upload, :download
944
1042
  # * +recursive+ should be true for directories and false for files.
945
1043
  # * +files+ is an Array of file paths, the content is direction specific.
@@ -978,6 +1076,7 @@ module Rye
978
1076
  end
979
1077
 
980
1078
  elsif direction == :upload
1079
+ # p :UPLOAD, @rye_templates
981
1080
  raise "Cannot upload to a StringIO object" if target.is_a?(StringIO)
982
1081
  if files.size == 1
983
1082
  target = self.getenv['HOME'] || guess_user_home
@@ -989,7 +1088,9 @@ module Rye
989
1088
  # Expand fileglobs (e.g. path/*.rb becomes [path/1.rb, path/2.rb]).
990
1089
  # This should happen after checking files.size to determine the target
991
1090
  unless @rye_safe
992
- files.collect! { |file| Dir.glob File.expand_path(file) }
1091
+ files.collect! { |file|
1092
+ file.is_a?(StringIO) ? file : Dir.glob(File.expand_path(file))
1093
+ }
993
1094
  files.flatten!
994
1095
  end
995
1096
  end
@@ -1004,7 +1105,6 @@ module Rye
1004
1105
  end
1005
1106
  end
1006
1107
 
1007
- info "#{direction.to_s} to: #{target}"
1008
1108
  debug "FILES: " << files.join(', ')
1009
1109
 
1010
1110
  # Make sure the target directory exists. We can do this only when
@@ -1020,22 +1120,16 @@ module Rye
1020
1120
  files.each do |file|
1021
1121
  debug file.to_s
1022
1122
  prev = ""
1023
- unless @rye_quiet
1024
- tmp_file = file.is_a?(StringIO) ? '<string>' : file
1025
- tmp_target = target.is_a?(StringIO) ? '<string>' : target
1026
- STDOUT.puts "[#{direction}] #{tmp_file} #{tmp_target}"
1027
- end
1123
+ line = nil
1028
1124
  transfers << scp.send(direction, file, target, :recursive => recursive) do |ch, n, s, t|
1029
1125
  line = "%-50s %6d/%-6d bytes" % [n, s, t]
1030
1126
  spaces = (prev.size > line.size) ? ' '*(prev.size - line.size) : ''
1031
- pinfo "%s %s \r" % [line, spaces] # update line: "file: sent/total"
1127
+ pinfo "[%s] %s %s %s" % [direction, line, spaces, s == t ? "\n" : "\r"] # update line: "file: sent/total"
1032
1128
  @rye_info.flush if @rye_info # make sure every line is printed
1033
1129
  prev = line
1034
1130
  end
1035
1131
  end
1036
1132
  transfers.each { |t| t.wait } # Run file transfers in parallel
1037
- pinfo (' '*prev.size) << "\r"
1038
- info $/
1039
1133
  end
1040
1134
 
1041
1135
  target.is_a?(StringIO) ? target : nil