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.
- data/CHANGES.txt +16 -0
- data/lib/rye.rb +0 -3
- data/lib/rye/box.rb +101 -68
- data/lib/rye/cmd.rb +45 -2
- data/rye.gemspec +1 -1
- 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
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
|
-
|
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 @
|
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
|
-
|
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
|
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.
|
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"
|