rye 0.7.2 → 0.7.3

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 (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 +2 -2
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: 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
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-06-01 00:00:00 -04:00
12
+ date: 2009-06-03 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency