rye 0.8.19 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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