delano-rye 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/CHANGES.txt +16 -0
  2. data/lib/rye.rb +0 -3
  3. data/lib/rye/box.rb +101 -68
  4. data/lib/rye/cmd.rb +45 -2
  5. data/rye.gemspec +1 -1
  6. metadata +1 -1
data/CHANGES.txt CHANGED
@@ -7,6 +7,22 @@ TODO
7
7
  * Add S3 support for Rye::Box.upload / download
8
8
 
9
9
 
10
+ #### 0.7.3 (2009-06-03) #############################
11
+
12
+ * ADDED: enable_safe_mode and disable_safe_mode methods
13
+ * ADDED: New default Rye::Cmd methods: gzip, bzip, tar and all derivatives. ./configure and make too.
14
+ * ADDED: Rye::Cmd#safely and Rye::Cmd#unsafely methods
15
+ * ADDED: Rye::Cmd#digest_md5, Rye::Cmd#digest_sha1, Rye::Cmd#digest_sha2 methods
16
+ * ADDED: Rye::Cmd#file_verified? method
17
+ * ADDED: Rye::Box#net_ssh_exec! now checks and correctly prompts for a sudo response password.
18
+ Note: this will be upgraded in for 0.8 to support any prompt.
19
+ * FIXED: Net::SSH paranoid now set to false when safe mode is disabled.
20
+
21
+
22
+ #### 0.7.2 (2009-06-01) #############################
23
+
24
+ *A re-release of 0.7.1 to force Rubyforge to update the gem*
25
+
10
26
  #### 0.7.1 (2009-06-01) #############################
11
27
 
12
28
  * CHANGE: Removed broken grep method from Rye::Rap
data/lib/rye.rb CHANGED
@@ -113,9 +113,6 @@ module Rye
113
113
  files || []
114
114
  end
115
115
 
116
-
117
-
118
-
119
116
  # Add one or more private keys to the SSH Agent.
120
117
  # * +keys+ one or more file paths to private keys used for passwordless logins.
121
118
  def add_keys(*keys)
data/lib/rye/box.rb CHANGED
@@ -36,8 +36,10 @@ module Rye
36
36
 
37
37
  def host=(val); @rye_host = val; end
38
38
  def opts=(val); @rye_opts = val; end
39
- def safe=(val); @rye_safe = val; end
40
-
39
+
40
+ def enable_safe_mode; @rye_safe = true; end
41
+ def disable_safe_mode; @rye_safe = false; end
42
+
41
43
  # The most recent value from Box.cd or Box.[]
42
44
  def current_working_directory; @rye_current_working_directory; end
43
45
 
@@ -100,7 +102,7 @@ module Rye
100
102
  @rye_info = STDOUT if @rye_info == true
101
103
 
102
104
  @rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
103
- @rye_opts[:paranoid] = true unless @rye_opts[:safe] == false # See Net::SSH.start
105
+ @rye_opts[:paranoid] = true unless @rye_safe == false # See Net::SSH.start
104
106
 
105
107
  # Add the given private keys to the keychain that will be used for @rye_host
106
108
  add_keys(@rye_opts[:keys])
@@ -169,57 +171,6 @@ module Rye
169
171
  end
170
172
 
171
173
 
172
- # Open an SSH session with +@rye_host+. This called automatically
173
- # when you the first comamnd is run if it's not already connected.
174
- # Raises a Rye::NoHost exception if +@rye_host+ is not specified.
175
- # Will attempt a password login up to 3 times if the initial
176
- # authentication fails.
177
- # * +reconnect+ Disconnect first if already connected. The default
178
- # is true. When set to false, connect will do nothing if already
179
- # connected.
180
- def connect(reconnect=true)
181
- raise Rye::NoHost unless @rye_host
182
- return if @rye_ssh && !reconnect
183
- disconnect if @rye_ssh
184
- debug "Opening connection to #{@rye_host} as #{@rye_opts[:user]}"
185
- highline = HighLine.new # Used for password prompt
186
- retried = 0
187
-
188
- begin
189
- @rye_ssh = Net::SSH.start(@rye_host, @rye_opts[:user], @rye_opts || {})
190
- rescue Net::SSH::HostKeyMismatch => ex
191
- STDERR.puts ex.message
192
- STDERR.puts "NOTE: EC2 instances generate new SSH keys on first boot."
193
- print "\a" if @rye_info # Ring the bell
194
- if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i)
195
- @rye_opts[:paranoid] = false
196
- retry
197
- else
198
- raise Net::SSH::HostKeyMismatch
199
- end
200
- rescue Net::SSH::AuthenticationFailed => ex
201
- print "\a" if retried == 0 && @rye_info # Ring the bell once
202
- retried += 1
203
- if STDIN.tty? && retried <= 3
204
- STDERR.puts "Passwordless login failed for #{@rye_opts[:user]}"
205
- @rye_opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
206
- @rye_opts[:auth_methods] ||= []
207
- @rye_opts[:auth_methods] << 'password'
208
- retry
209
- else
210
- raise Net::SSH::AuthenticationFailed
211
- end
212
- end
213
-
214
- # We add :auth_methods (a Net::SSH joint) to force asking for a
215
- # password if the initial (key-based) authentication fails. We
216
- # need to delete the key from @rye_opts otherwise it lingers until
217
- # the next connection (if we switch_user is called for example).
218
- @rye_opts.delete :auth_methods if @rye_opts.has_key?(:auth_methods)
219
-
220
- self
221
- end
222
-
223
174
  # Reconnect as another user. This is different from su=
224
175
  # which executes subsequent commands via +su -c COMMAND USER+.
225
176
  # * +newuser+ The username to reconnect as
@@ -509,6 +460,26 @@ module Rye
509
460
  self.instance_exec *args, &block
510
461
  end
511
462
 
463
+ # Like batch, except it disables safe mode before executing the block.
464
+ # After executing the block, safe mode is returned back to whichever
465
+ # state it was previously in. In other words, this method won't enable
466
+ # safe mode if it was already disabled.
467
+ def unsafely(*args, &block)
468
+ previous_state = @rye_safe
469
+ disable_safe_mode
470
+ self.instance_exec *args, &block
471
+ @rye_safe = previous_state
472
+ end
473
+
474
+ # See unsafely (except in reverse)
475
+ def safely(*args, &block)
476
+ previous_state = @rye_safe
477
+ enable_safe_mode
478
+ self.instance_exec *args, &block
479
+ @rye_safe = previous_state
480
+ end
481
+
482
+
512
483
  # instance_exec for Ruby 1.8 written by Mauricio Fernandez
513
484
  # http://eigenclass.org/hiki/instance_exec
514
485
  if RUBY_VERSION =~ /1.8/
@@ -539,6 +510,59 @@ module Rye
539
510
  end
540
511
 
541
512
 
513
+
514
+ # Open an SSH session with +@rye_host+. This called automatically
515
+ # when you the first comamnd is run if it's not already connected.
516
+ # Raises a Rye::NoHost exception if +@rye_host+ is not specified.
517
+ # Will attempt a password login up to 3 times if the initial
518
+ # authentication fails.
519
+ # * +reconnect+ Disconnect first if already connected. The default
520
+ # is true. When set to false, connect will do nothing if already
521
+ # connected.
522
+ def connect(reconnect=true)
523
+ raise Rye::NoHost unless @rye_host
524
+ return if @rye_ssh && !reconnect
525
+ disconnect if @rye_ssh
526
+ debug "Opening connection to #{@rye_host} as #{@rye_opts[:user]}"
527
+ highline = HighLine.new # Used for password prompt
528
+ retried = 0
529
+
530
+ begin
531
+ @rye_ssh = Net::SSH.start(@rye_host, @rye_opts[:user], @rye_opts || {})
532
+ rescue Net::SSH::HostKeyMismatch => ex
533
+ STDERR.puts ex.message
534
+ STDERR.puts "NOTE: EC2 instances generate new SSH keys on first boot."
535
+ print "\a" if @rye_info # Ring the bell
536
+ if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i)
537
+ @rye_opts[:paranoid] = false
538
+ retry
539
+ else
540
+ raise Net::SSH::HostKeyMismatch
541
+ end
542
+ rescue Net::SSH::AuthenticationFailed => ex
543
+ print "\a" if retried == 0 && @rye_info # Ring the bell once
544
+ retried += 1
545
+ if STDIN.tty? && retried <= 3
546
+ STDERR.puts "Passwordless login failed for #{@rye_opts[:user]}"
547
+ @rye_opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
548
+ @rye_opts[:auth_methods] ||= []
549
+ @rye_opts[:auth_methods] << 'password'
550
+ retry
551
+ else
552
+ raise Net::SSH::AuthenticationFailed
553
+ end
554
+ end
555
+
556
+ # We add :auth_methods (a Net::SSH joint) to force asking for a
557
+ # password if the initial (key-based) authentication fails. We
558
+ # need to delete the key from @rye_opts otherwise it lingers until
559
+ # the next connection (if we switch_user is called for example).
560
+ @rye_opts.delete :auth_methods if @rye_opts.has_key?(:auth_methods)
561
+
562
+ self
563
+ end
564
+
565
+
542
566
  private
543
567
 
544
568
  def debug(msg="unknown debug msg"); @rye_debug.puts msg if @rye_debug; end
@@ -582,7 +606,7 @@ module Rye
582
606
 
583
607
  connect if !@rye_ssh || @rye_ssh.closed?
584
608
  raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
585
-
609
+
586
610
  cmd_clean = Rye.escape(@rye_safe, cmd, args)
587
611
  cmd_clean = prepend_env(cmd_clean)
588
612
 
@@ -660,6 +684,14 @@ module Rye
660
684
 
661
685
  # Executes +command+ via SSH
662
686
  # Returns an Array with 4 elements: [stdout, stderr, exit code, exit signal]
687
+ # NOTE: This method needs to be replaced to fully support interactive
688
+ # commands. This implementation is weird because it's getting just STDOUT and
689
+ # STDERR responses (check value of "type"). on_data and on_extended_data method
690
+ # hooks are not used. See the following threads for implementation ideas:
691
+ #
692
+ # http://www.ruby-forum.com/topic/141814
693
+ # http://www.ruby-forum.com/topic/169997
694
+ #
663
695
  def net_ssh_exec!(command)
664
696
 
665
697
  block ||= Proc.new do |channel, type, data|
@@ -667,7 +699,16 @@ module Rye
667
699
  channel[:stderr] ||= ""
668
700
  channel[:exit_code] ||= 0
669
701
  channel[:stdout] << data if type == :stdout
670
- channel[:stderr] << data if type == :stderr
702
+ if type == :stderr
703
+ # NOTE: Use sudo to test this since it prompts for a passwords.
704
+ # Use sudo -K to kill the user's timestamp (ask for a password every time)
705
+ if data =~ /Password:/
706
+ ret = Annoy.get_user_input("Password: ", '*')
707
+ channel.send_data "#{ret}\n"
708
+ else
709
+ channel[:stderr] << data
710
+ end
711
+ end
671
712
  channel.on_request("exit-status") do |ch, data|
672
713
  # Anything greater than 0 is an error
673
714
  channel[:exit_code] = data.read_long
@@ -676,22 +717,14 @@ module Rye
676
717
  # This should be the POSIX SIGNAL that ended the process
677
718
  channel[:exit_signal] = data.read_long
678
719
  end
679
- # For long-running commands like top, this will print the output.
680
- # It's cool, but we'd also need to enable STDIN to interact with
681
- # command.
682
- #channel.on_data do |ch, data|
683
- # puts "got stdout: #{data}"
684
- # #channel.send_data "something for stdin\n"
685
- #end
686
- #
687
- #channel.on_extended_data do |ch, data|
688
- # #puts "got stdout: #{data}"
689
- # #channel.send_data "something for stdin\n"
690
- #end
691
720
  end
692
721
 
693
722
  channel = @rye_ssh.exec(command, &block)
723
+
694
724
  channel.wait # block until we get a response
725
+ channel.request_pty do |ch, success|
726
+ raise "Could not obtain pty (i.e. an interactive ssh session)" if !success
727
+ end
695
728
 
696
729
  channel[:exit_code] = 0 if channel[:exit_code] == nil
697
730
  channel[:exit_code] &&= channel[:exit_code].to_i
data/lib/rye/cmd.rb CHANGED
@@ -43,6 +43,7 @@ module Rye;
43
43
  def sed(*args); cmd('sed', args); end
44
44
  def awk(*args); cmd('awk', args); end
45
45
  def cat(*args); cmd('cat', args); end
46
+ def tar(*args); cmd('tar', args); end
46
47
 
47
48
  #def kill(*args); cmd('kill', args); end
48
49
  def sudo(*args); cmd('sudo', args); end
@@ -54,6 +55,8 @@ module Rye;
54
55
  def echo(*args); cmd('echo', args); end
55
56
  def test(*args); cmd('test', args); end
56
57
  def mkfs(*args); cmd('mkfs', args); end
58
+ def gzip(*args); cmd('gzip', args); end
59
+ def make(*args); cmd('make', args); end
57
60
 
58
61
  def mount(*args); cmd("mount", args); end
59
62
  def sleep(*args); cmd("sleep", args); end
@@ -62,15 +65,20 @@ module Rye;
62
65
  def uname(*args); cmd('uname', args); end
63
66
  def chmod(*args); cmd('chmod', args); end
64
67
  def chown(*args); cmd('chown', args); end
68
+ def unzip(*args); cmd('unzip', args); end
69
+ def bzip2(*args); cmd('bzip2', args); end
65
70
 
66
71
  def umount(*args); cmd("umount", args); end
67
72
  def uptime(*args); cmd("uptime", args); end
68
73
  def python(*args); cmd('python', args); end
74
+ def gunzip(*args); cmd('gunzip', args); end
69
75
  def useradd(*args); cmd('useradd', args); end
76
+ def bunzip2(*args); cmd('bunzip2', args); end
70
77
  def getconf(*args); cmd('getconf', args); end
71
78
  def history(*args); cmd('history', args); end
72
79
  def printenv(*args); cmd('printenv', args); end
73
80
  def hostname(*args); cmd('hostname', args); end
81
+ def configure(*args); cmd('./configure', args); end
74
82
 
75
83
  # Transfer files to a machine via Net::SCP.
76
84
  # * +files+ is an Array of files to upload. The last element is the
@@ -118,7 +126,7 @@ module Rye;
118
126
  self.upload file_content, filepath
119
127
  end
120
128
 
121
- # Does a remote path exist?
129
+ # Does +path+ from the current working directory?
122
130
  def file_exists?(path)
123
131
  begin
124
132
  ret = self.ls(path)
@@ -129,7 +137,42 @@ module Rye;
129
137
  # But on OSX exit code is 1. This is why we look at STDERR.
130
138
  ret.stderr.empty?
131
139
  end
132
-
140
+
141
+ # Does the calculated digest of +path+ match the known +expected_digest+?
142
+ # This is useful for verifying downloaded files.
143
+ # +digest_type+ must be one of: :md5, :sha1, :sha2
144
+ def file_verified?(path, expected_digest, digest_type=:md5)
145
+ return false unless file_exists?(path)
146
+ raise "Unknown disgest type: #{digest_type}" unless can?("digest_#{digest_type}")
147
+ digest = self.send("digest_#{digest_type}", path).first
148
+ info "#{digest_type} (#{path}) = #{digest}"
149
+ digest.to_s == expected_digest.to_s
150
+ end
151
+
152
+ # * +files+ An Array of file paths
153
+ # Returns an Array of MD5 digests for each of the given files
154
+ def digest_md5(*files)
155
+ files.flatten.collect { |file|
156
+ File.exists?(file) ? Digest::MD5.hexdigest(File.read(file)) : nil
157
+ }
158
+ end
159
+
160
+ # * +files+ An Array of file paths
161
+ # Returns an Array of SH1 digests for each of the given files
162
+ def digest_sha1(*files)
163
+ files.flatten.collect { |file|
164
+ File.exists?(file) ? Digest::SHA1.hexdigest(File.read(file)) : nil
165
+ }
166
+ end
167
+
168
+ # * +files+ An Array of file paths
169
+ # Returns an Array of SH2 digests for each of the given files
170
+ def digest_sha2(*files)
171
+ files.flatten.collect { |file|
172
+ File.exists?(file) ? Digest::SHA2.hexdigest(File.read(file)) : nil
173
+ }
174
+ end
175
+
133
176
  # Returns an Array of system commands available over SSH
134
177
  def can
135
178
  Rye::Cmd.instance_methods
data/rye.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = "rye"
3
3
  s.rubyforge_project = "rye"
4
- s.version = "0.7.2"
4
+ s.version = "0.7.3"
5
5
  s.summary = "Rye: Safely run SSH commands on a bunch of machines at the same time (from Ruby)."
6
6
  s.description = s.summary
7
7
  s.author = "Delano Mandelbaum"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delano-rye
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum