delano-rye 0.6.6 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +9 -2
- data/README.rdoc +3 -2
- data/lib/rye.rb +1 -1
- data/lib/rye/box.rb +159 -105
- data/lib/rye/cmd.rb +18 -35
- data/rye.gemspec +1 -1
- metadata +1 -1
data/CHANGES.txt
CHANGED
@@ -4,13 +4,20 @@ TODO
|
|
4
4
|
|
5
5
|
* Re-implement Rye::Rap as an Observable StringIO object for dynamic printing of output.
|
6
6
|
* Fingerprints: ssh-keygen -l -f id_rsa_repos.pub
|
7
|
+
* Add S3 support for Rye::Box.upload / download
|
8
|
+
|
9
|
+
|
10
|
+
#### 0.7.0 (2009-05-30) #############################
|
11
|
+
|
12
|
+
* CHANGE: Rye::Box now uses unique instance variable names to encourage using
|
13
|
+
instance variables in batch command blocks.
|
14
|
+
* ADDED: Rye::Box#file_append
|
7
15
|
|
8
16
|
|
9
17
|
#### 0.6.6 (2009-05-21) #############################
|
10
18
|
|
11
19
|
* CHANGE: Key management is handled by ssh-agent again (instead of Net::SSH)
|
12
20
|
|
13
|
-
|
14
21
|
#### 0.6.5 (2009-05-10) #############################
|
15
22
|
|
16
23
|
* CHANGE: Default exit code is now 0 instead of -1
|
@@ -37,7 +44,6 @@ TODO
|
|
37
44
|
|
38
45
|
* FIXED: I forgot to add highline to the gemspec file manifest. Cripes!
|
39
46
|
|
40
|
-
|
41
47
|
#### 0.6.1 (2009-04-29) #############################
|
42
48
|
|
43
49
|
* ADDED: Prints message to STDERR when passwordless login fails.
|
@@ -45,6 +51,7 @@ TODO
|
|
45
51
|
1.5.1 is not released as a gem yet)
|
46
52
|
* CHANGE: Cleaned examples and links in README
|
47
53
|
|
54
|
+
|
48
55
|
#### 0.6.0 (2009-04-28) #############################
|
49
56
|
|
50
57
|
* FIXED: handling of Process::Status ($?) in Rye.shell
|
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Rye - v0.
|
1
|
+
= Rye - v0.7
|
2
2
|
|
3
3
|
Safely run SSH commands on a bunch of machines at the same time (from Ruby).
|
4
4
|
|
@@ -132,7 +132,8 @@ This list will grow. If you find one let me know!
|
|
132
132
|
== Credits
|
133
133
|
|
134
134
|
* Delano Mandelbaum (delano@solutious.com)
|
135
|
-
* Escape, Copyright (C) 2006,2007 Tanaka Akira
|
135
|
+
* Escape, Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
|
136
|
+
* Rye::Box#instance_exec (for Ruby 1.8) Mauricio Fernandez
|
136
137
|
|
137
138
|
== License
|
138
139
|
|
data/lib/rye.rb
CHANGED
data/lib/rye/box.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module Rye
|
4
4
|
|
5
|
-
|
6
5
|
# = Rye::Box
|
7
6
|
#
|
8
7
|
# The Rye::Box class represents a machine. All system
|
@@ -30,25 +29,28 @@ module Rye
|
|
30
29
|
class Box
|
31
30
|
include Rye::Cmd
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
32
|
+
def host; @rye_host; end
|
33
|
+
def opts; @rye_opts; end
|
34
|
+
def safe; @rye_safe; end
|
35
|
+
|
36
|
+
def host=(val); @rye_host = val; end
|
37
|
+
def opts=(val); @rye_opts = val; end
|
38
|
+
def safe=(val); @rye_safe = val; end
|
39
|
+
|
40
|
+
# The most recent value from Box.cd or Box.[]
|
41
|
+
def current_working_directory; @rye_current_working_directory; end
|
42
|
+
|
43
|
+
# The most recent valud for umask (or 0022)
|
44
|
+
def current_umask; @rye_current_umask; end
|
45
|
+
|
46
|
+
def ssh; @rye_ssh; end
|
47
|
+
def info; @rye_info; end
|
48
|
+
def debug; @rye_debug; end
|
49
|
+
def error; @rye_error; end
|
50
|
+
|
51
|
+
def pre_command_hook=(val); @rye_pre_command_hook = val; end
|
52
|
+
def post_command_hook=(val); @rye_post_command_hook = val; end
|
53
|
+
|
52
54
|
# * +host+ The hostname to connect to. The default is localhost.
|
53
55
|
# * +opts+ a hash of optional arguments.
|
54
56
|
#
|
@@ -67,10 +69,10 @@ module Rye
|
|
67
69
|
# Net::SSH.start that is not already mentioned above.
|
68
70
|
#
|
69
71
|
def initialize(host='localhost', opts={})
|
70
|
-
@
|
72
|
+
@rye_host = host
|
71
73
|
|
72
74
|
# These opts are use by Rye::Box and also passed to Net::SSH
|
73
|
-
@
|
75
|
+
@rye_opts = {
|
74
76
|
:user => Rye.sysinfo.user,
|
75
77
|
:safe => true,
|
76
78
|
:port => 22,
|
@@ -85,33 +87,33 @@ module Rye
|
|
85
87
|
# if disconnect has already been called explicitly.
|
86
88
|
at_exit { self.disconnect }
|
87
89
|
|
88
|
-
# @
|
90
|
+
# @rye_opts gets sent to Net::SSH so we need to remove the keys
|
89
91
|
# that are not meant for it.
|
90
|
-
@
|
91
|
-
@
|
92
|
-
@
|
92
|
+
@rye_safe, @rye_debug = @rye_opts.delete(:safe), @rye_opts.delete(:debug)
|
93
|
+
@rye_info, @rye_error = @rye_opts.delete(:info), @rye_opts.delete(:error)
|
94
|
+
@rye_getenv = {} if @rye_opts.delete(:getenv) # Enable getenv with a hash
|
93
95
|
|
94
96
|
# Just in case someone sends a true value rather than IO object
|
95
|
-
@
|
96
|
-
@
|
97
|
-
@
|
97
|
+
@rye_debug = STDERR if @rye_debug == true
|
98
|
+
@rye_error = STDERR if @rye_error == true
|
99
|
+
@rye_info = STDOUT if @rye_info == true
|
98
100
|
|
99
|
-
@
|
100
|
-
@
|
101
|
+
@rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
|
102
|
+
@rye_opts[:paranoid] = true unless @rye_opts[:safe] == false # See Net::SSH.start
|
101
103
|
|
102
|
-
# Add the given private keys to the keychain that will be used for @
|
103
|
-
add_keys(@
|
104
|
+
# Add the given private keys to the keychain that will be used for @rye_host
|
105
|
+
add_keys(@rye_opts[:keys])
|
104
106
|
|
105
107
|
# We don't want Net::SSH to handle the keypairs. This may change
|
106
108
|
# but for we're letting ssh-agent do it.
|
107
109
|
# TODO: Check if this should ot should not be enabled.
|
108
|
-
@
|
110
|
+
@rye_opts.delete(:keys)
|
109
111
|
|
110
112
|
# From: capistrano/lib/capistrano/cli.rb
|
111
113
|
STDOUT.sync = true # so that Net::SSH prompts show up
|
112
114
|
|
113
115
|
debug "ssh-agent info: #{Rye.sshagent_info.inspect}"
|
114
|
-
debug @
|
116
|
+
debug @rye_opts.inspect
|
115
117
|
|
116
118
|
end
|
117
119
|
|
@@ -135,61 +137,61 @@ module Rye
|
|
135
137
|
# rbox.pwd # => /usr/bin ($ cd /usr/bin && pwd)
|
136
138
|
#
|
137
139
|
def [](key=nil)
|
138
|
-
@
|
140
|
+
@rye_current_working_directory = key
|
139
141
|
self
|
140
142
|
end
|
141
143
|
# Like [] except it returns an empty Rye::Rap object to mimick
|
142
144
|
# a regular command method. Call with nil key (or no arg) to
|
143
145
|
# reset.
|
144
146
|
def cd(key=nil)
|
145
|
-
@
|
147
|
+
@rye_current_working_directory = key
|
146
148
|
ret = Rye::Rap.new(self)
|
147
149
|
end
|
148
150
|
|
149
151
|
# Change the current umask (sort of -- works the same way as cd)
|
150
152
|
# The default umask is 0022
|
151
153
|
def umask=(val='0022')
|
152
|
-
@
|
154
|
+
@rye_current_umask = val
|
153
155
|
self
|
154
156
|
end
|
155
157
|
|
156
158
|
|
157
|
-
# Open an SSH session with +@
|
159
|
+
# Open an SSH session with +@rye_host+. This called automatically
|
158
160
|
# when you the first comamnd is run if it's not already connected.
|
159
|
-
# Raises a Rye::NoHost exception if +@
|
161
|
+
# Raises a Rye::NoHost exception if +@rye_host+ is not specified.
|
160
162
|
# Will attempt a password login up to 3 times if the initial
|
161
163
|
# authentication fails.
|
162
164
|
# * +reconnect+ Disconnect first if already connected. The default
|
163
165
|
# is true. When set to false, connect will do nothing if already
|
164
166
|
# connected.
|
165
167
|
def connect(reconnect=true)
|
166
|
-
raise Rye::NoHost unless @
|
167
|
-
return if @
|
168
|
-
disconnect if @
|
169
|
-
debug "Opening connection to #{@
|
168
|
+
raise Rye::NoHost unless @rye_host
|
169
|
+
return if @rye_ssh && !reconnect
|
170
|
+
disconnect if @rye_ssh
|
171
|
+
debug "Opening connection to #{@rye_host} as #{@rye_opts[:user]}"
|
170
172
|
highline = HighLine.new # Used for password prompt
|
171
173
|
retried = 0
|
172
174
|
|
173
175
|
begin
|
174
|
-
@
|
176
|
+
@rye_ssh = Net::SSH.start(@rye_host, @rye_opts[:user], @rye_opts || {})
|
175
177
|
rescue Net::SSH::HostKeyMismatch => ex
|
176
178
|
STDERR.puts ex.message
|
177
179
|
STDERR.puts "NOTE: EC2 instances generate new SSH keys on first boot."
|
178
|
-
print "\a" if @
|
180
|
+
print "\a" if @rye_info # Ring the bell
|
179
181
|
if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i)
|
180
|
-
@
|
182
|
+
@rye_opts[:paranoid] = false
|
181
183
|
retry
|
182
184
|
else
|
183
185
|
raise Net::SSH::HostKeyMismatch
|
184
186
|
end
|
185
187
|
rescue Net::SSH::AuthenticationFailed => ex
|
186
|
-
print "\a" if retried == 0 && @
|
188
|
+
print "\a" if retried == 0 && @rye_info # Ring the bell once
|
187
189
|
retried += 1
|
188
190
|
if STDIN.tty? && retried <= 3
|
189
|
-
STDERR.puts "Passwordless login failed for #{@
|
190
|
-
@
|
191
|
-
@
|
192
|
-
@
|
191
|
+
STDERR.puts "Passwordless login failed for #{@rye_opts[:user]}"
|
192
|
+
@rye_opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
|
193
|
+
@rye_opts[:auth_methods] ||= []
|
194
|
+
@rye_opts[:auth_methods] << 'password'
|
193
195
|
retry
|
194
196
|
else
|
195
197
|
raise Net::SSH::AuthenticationFailed
|
@@ -198,9 +200,9 @@ module Rye
|
|
198
200
|
|
199
201
|
# We add :auth_methods (a Net::SSH joint) to force asking for a
|
200
202
|
# password if the initial (key-based) authentication fails. We
|
201
|
-
# need to delete the key from @
|
203
|
+
# need to delete the key from @rye_opts otherwise it lingers until
|
202
204
|
# the next connection (if we switch_user is called for example).
|
203
|
-
@
|
205
|
+
@rye_opts.delete :auth_methods if @rye_opts.has_key?(:auth_methods)
|
204
206
|
|
205
207
|
self
|
206
208
|
end
|
@@ -213,20 +215,20 @@ module Rye
|
|
213
215
|
# and a new one is opened for the given user.
|
214
216
|
def switch_user(newuser)
|
215
217
|
return if newuser.to_s == self.user.to_s
|
216
|
-
@
|
217
|
-
@
|
218
|
+
@rye_opts ||= {}
|
219
|
+
@rye_opts[:user] = newuser
|
218
220
|
disconnect
|
219
221
|
connect
|
220
222
|
end
|
221
223
|
|
222
224
|
|
223
|
-
# Close the SSH session with +@
|
225
|
+
# Close the SSH session with +@rye_host+. This is called
|
224
226
|
# automatically at exit if the connection is open.
|
225
227
|
def disconnect
|
226
|
-
return unless @
|
227
|
-
@
|
228
|
-
debug "Closing connection to #{@
|
229
|
-
@
|
228
|
+
return unless @rye_ssh && !@rye_ssh.closed?
|
229
|
+
@rye_ssh.loop(0.1) { @rye_ssh.busy? }
|
230
|
+
debug "Closing connection to #{@rye_ssh.host}"
|
231
|
+
@rye_ssh.close
|
230
232
|
end
|
231
233
|
|
232
234
|
|
@@ -239,7 +241,7 @@ module Rye
|
|
239
241
|
def interactive_ssh(run=true)
|
240
242
|
debug "interactive_ssh with keys: #{Rye.keys.inspect}"
|
241
243
|
run = false unless STDIN.tty?
|
242
|
-
cmd = Rye.prepare_command("ssh", "#{@
|
244
|
+
cmd = Rye.prepare_command("ssh", "#{@rye_opts[:user]}@rye_#{@rye_host}")
|
243
245
|
return cmd unless run
|
244
246
|
system(cmd)
|
245
247
|
end
|
@@ -260,44 +262,79 @@ module Rye
|
|
260
262
|
end
|
261
263
|
alias :add_key :add_keys
|
262
264
|
|
265
|
+
# Return the value of uname in lowercase
|
266
|
+
# This is a temporary fix. We can use SysInfo for this, upload
|
267
|
+
# it, execute it directly, parse the output.
|
268
|
+
def ostype
|
269
|
+
return @rye_ostype if @rye_ostype # simple cache
|
270
|
+
os = self.uname.first rescue nil
|
271
|
+
os ||= 'unknown'
|
272
|
+
os &&= os.downcase
|
273
|
+
@rye_ostype = os
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns the hash containing the parsed output of "env" on the
|
277
|
+
# remote machine. If the initialize option +:getenv+ was set to
|
278
|
+
# false, this will return an empty hash.
|
279
|
+
# This is a lazy loaded method so it fetches the remote envvars
|
280
|
+
# the first time this method is called.
|
281
|
+
#
|
282
|
+
# puts rbox.getenv['HOME'] # => "/home/gloria" (remote)
|
283
|
+
#
|
284
|
+
# NOTE: This method should not raise an exception under normal
|
285
|
+
# circumstances.
|
286
|
+
#
|
287
|
+
def getenv
|
288
|
+
if @rye_getenv && @rye_getenv.empty? && self.can?(:env)
|
289
|
+
env = self.env rescue []
|
290
|
+
env.each do |nv|
|
291
|
+
# Parse "GLORIA_HOME=/gloria/lives/here" into a name/value
|
292
|
+
# pair. The regexp ensures we split only at the 1st = sign
|
293
|
+
n, v = nv.scan(/\A([\w_-]+?)=(.+)\z/).flatten
|
294
|
+
@rye_getenv[n] = v
|
295
|
+
end
|
296
|
+
end
|
297
|
+
@rye_getenv
|
298
|
+
end
|
299
|
+
|
263
300
|
# Add an environment variable. +n+ and +v+ are the name and value.
|
264
301
|
# Returns the instance of Rye::Box
|
265
302
|
def setenv(n, v)
|
266
303
|
debug "Adding env: #{n}=#{v}"
|
267
|
-
debug "prev value: #{@
|
268
|
-
@
|
269
|
-
(@
|
304
|
+
debug "prev value: #{@rye_getenv[n]}"
|
305
|
+
@rye_getenv[n] = v
|
306
|
+
(@rye_current_environment_variables ||= {})[n] = v
|
270
307
|
self
|
271
308
|
end
|
272
309
|
alias :add_env :setenv # deprecated?
|
273
310
|
|
274
311
|
# The name of the user that opened the SSH connection
|
275
|
-
def user; (@
|
312
|
+
def user; (@rye_opts || {})[:user]; end
|
276
313
|
|
277
314
|
# See Rye.keys
|
278
315
|
def keys; Rye.keys; end
|
279
316
|
|
280
|
-
# Returns +user@
|
281
|
-
def to_s; '%s
|
317
|
+
# Returns +user@rye_host+
|
318
|
+
def to_s; '%s@rye_%s' % [user, @rye_host]; end
|
282
319
|
|
283
320
|
def inspect
|
284
321
|
%q{#<%s:%s cwd=%s umask=%s env=%s safe=%s opts=%s>} %
|
285
322
|
[self.class.to_s, self.host,
|
286
|
-
@
|
287
|
-
(@
|
323
|
+
@rye_current_working_directory, @rye_current_umask,
|
324
|
+
(@rye_current_environment_variables || '').inspect,
|
288
325
|
self.safe, self.opts.inspect]
|
289
326
|
end
|
290
327
|
|
291
328
|
# Compares itself with the +other+ box. If the hostnames
|
292
329
|
# are the same, this will return true. Otherwise false.
|
293
330
|
def ==(other)
|
294
|
-
@
|
331
|
+
@rye_host == other.host
|
295
332
|
end
|
296
333
|
|
297
334
|
# Returns the host SSH keys for this box
|
298
335
|
def host_key
|
299
|
-
raise "No host" unless @
|
300
|
-
Rye.remote_host_keys(@
|
336
|
+
raise "No host" unless @rye_host
|
337
|
+
Rye.remote_host_keys(@rye_host)
|
301
338
|
end
|
302
339
|
|
303
340
|
# Uses the output of "useradd -D" to determine the default home
|
@@ -305,10 +342,10 @@ module Rye
|
|
305
342
|
# home directory. Currently used only by authorize_keys_remote.
|
306
343
|
def guess_user_home(other_user=nil)
|
307
344
|
this_user = other_user || opts[:user]
|
308
|
-
@
|
345
|
+
@rye_guessed_homes ||= {}
|
309
346
|
|
310
347
|
# A simple cache.
|
311
|
-
return @
|
348
|
+
return @rye_guessed_homes[this_user] if @rye_guessed_homes.has_key?(this_user)
|
312
349
|
|
313
350
|
# Some junk to determine where user home directories are by default.
|
314
351
|
# We're relying on the command "useradd -D" so this may not work on
|
@@ -337,7 +374,7 @@ module Rye
|
|
337
374
|
end
|
338
375
|
end
|
339
376
|
|
340
|
-
@
|
377
|
+
@rye_guessed_homes[this_user] = "#{user_defaults['HOME']}/#{this_user}"
|
341
378
|
end
|
342
379
|
|
343
380
|
# Copy the local public keys (as specified by Rye.keys) to
|
@@ -439,8 +476,8 @@ module Rye
|
|
439
476
|
# with three arguments: command name, an Array of arguments, user name
|
440
477
|
#
|
441
478
|
def pre_command_hook(&block)
|
442
|
-
@
|
443
|
-
@
|
479
|
+
@rye_pre_command_hook = block if block
|
480
|
+
@rye_pre_command_hook
|
444
481
|
end
|
445
482
|
|
446
483
|
# Execute a block in the context of an instance of Rye::Box.
|
@@ -455,8 +492,25 @@ module Rye
|
|
455
492
|
# rbox.batch(&block)
|
456
493
|
#
|
457
494
|
#
|
458
|
-
def batch(&block)
|
459
|
-
|
495
|
+
def batch(*args, &block)
|
496
|
+
self.instance_exec *args, &block
|
497
|
+
end
|
498
|
+
|
499
|
+
# instance_exec for Ruby 1.8 written by Mauricio Fernandez
|
500
|
+
# http://eigenclass.org/hiki/instance_exec
|
501
|
+
if RUBY_VERSION =~ /1.8/
|
502
|
+
module InstanceExecHelper; end
|
503
|
+
include InstanceExecHelper
|
504
|
+
def instance_exec(*args, &block) # !> method redefined; discarding old instance_exec
|
505
|
+
mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
|
506
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
507
|
+
begin
|
508
|
+
ret = send(mname, *args)
|
509
|
+
ensure
|
510
|
+
InstanceExecHelper.module_eval{ undef_method(mname) } rescue nil
|
511
|
+
end
|
512
|
+
ret
|
513
|
+
end
|
460
514
|
end
|
461
515
|
|
462
516
|
# Supply a block to be called after every command. It's called
|
@@ -467,23 +521,23 @@ module Rye
|
|
467
521
|
# behavior) so the block needs to check the Rye::Rap object to
|
468
522
|
# determine whether an exception should be raised.
|
469
523
|
def post_command_hook(&block)
|
470
|
-
@
|
471
|
-
@
|
524
|
+
@rye_post_command_hook = block if block
|
525
|
+
@rye_post_command_hook
|
472
526
|
end
|
473
527
|
|
474
528
|
|
475
529
|
private
|
476
530
|
|
477
|
-
def debug(msg="unknown debug msg"); @
|
478
|
-
def error(msg="unknown error msg"); @
|
479
|
-
def pinfo(msg="unknown info msg"); @
|
480
|
-
def info(msg="unknown info msg"); @
|
531
|
+
def debug(msg="unknown debug msg"); @rye_debug.puts msg if @rye_debug; end
|
532
|
+
def error(msg="unknown error msg"); @rye_error.puts msg if @rye_error; end
|
533
|
+
def pinfo(msg="unknown info msg"); @rye_info.print msg if @rye_info; end
|
534
|
+
def info(msg="unknown info msg"); @rye_info.puts msg if @rye_info; end
|
481
535
|
|
482
536
|
# Add the current environment variables to the beginning of +cmd+
|
483
537
|
def prepend_env(cmd)
|
484
|
-
return cmd unless @
|
538
|
+
return cmd unless @rye_current_environment_variables.is_a?(Hash)
|
485
539
|
env = ''
|
486
|
-
@
|
540
|
+
@rye_current_environment_variables.each_pair do |n,v|
|
487
541
|
env << "export #{n}=#{Escape.shell_single_word(v)}; "
|
488
542
|
end
|
489
543
|
[env, cmd].join(' ')
|
@@ -513,22 +567,22 @@ module Rye
|
|
513
567
|
|
514
568
|
cmd, args = prep_args(*args)
|
515
569
|
|
516
|
-
connect if !@
|
517
|
-
raise Rye::NotConnected, @
|
570
|
+
connect if !@rye_ssh || @rye_ssh.closed?
|
571
|
+
raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
|
518
572
|
|
519
|
-
cmd_clean = Rye.escape(@
|
573
|
+
cmd_clean = Rye.escape(@rye_safe, cmd, args)
|
520
574
|
cmd_clean = prepend_env(cmd_clean)
|
521
575
|
|
522
576
|
# Add the current working directory before the command if supplied.
|
523
577
|
# The command will otherwise run in the user's home directory.
|
524
|
-
if @
|
525
|
-
cwd = Rye.escape(@
|
578
|
+
if @rye_current_working_directory
|
579
|
+
cwd = Rye.escape(@rye_safe, 'cd', @rye_current_working_directory)
|
526
580
|
cmd_clean = [cwd, cmd_clean].join(' && ')
|
527
581
|
end
|
528
582
|
|
529
583
|
# ditto (same explanation as cwd)
|
530
|
-
if @
|
531
|
-
cwd = Rye.escape(@
|
584
|
+
if @rye_current_umask
|
585
|
+
cwd = Rye.escape(@rye_safe, 'umask', @rye_current_umask)
|
532
586
|
cmd_clean = [cwd, cmd_clean].join(' && ')
|
533
587
|
end
|
534
588
|
|
@@ -536,8 +590,8 @@ module Rye
|
|
536
590
|
info "COMMAND: #{cmd_clean}"
|
537
591
|
debug "Executing: %s" % cmd_clean
|
538
592
|
|
539
|
-
if @
|
540
|
-
@
|
593
|
+
if @rye_pre_command_hook.is_a?(Proc)
|
594
|
+
@rye_pre_command_hook.call(cmd, args, opts[:user])
|
541
595
|
end
|
542
596
|
|
543
597
|
## NOTE: Do not raise a CommandNotFound exception in this method.
|
@@ -558,8 +612,8 @@ module Rye
|
|
558
612
|
rap.exit_signal = esignal
|
559
613
|
rap.cmd = cmd
|
560
614
|
|
561
|
-
if @
|
562
|
-
@
|
615
|
+
if @rye_post_command_hook.is_a?(Proc)
|
616
|
+
@rye_post_command_hook.call(rap)
|
563
617
|
else
|
564
618
|
# It seems a convention for various commands to return -1
|
565
619
|
# when something only mildly concerning happens. ls even
|
@@ -623,7 +677,7 @@ module Rye
|
|
623
677
|
#end
|
624
678
|
end
|
625
679
|
|
626
|
-
channel = @
|
680
|
+
channel = @rye_ssh.exec(command, &block)
|
627
681
|
channel.wait # block until we get a response
|
628
682
|
|
629
683
|
channel[:exit_code] = 0 if channel[:exit_code] == nil
|
@@ -655,8 +709,8 @@ module Rye
|
|
655
709
|
raise "Must be one of: upload, download"
|
656
710
|
end
|
657
711
|
|
658
|
-
if @
|
659
|
-
info "CWD (#{@
|
712
|
+
if @rye_current_working_directory
|
713
|
+
info "CWD (#{@rye_current_working_directory})"
|
660
714
|
end
|
661
715
|
|
662
716
|
files = [files].flatten.compact || []
|
@@ -674,7 +728,7 @@ module Rye
|
|
674
728
|
raise "Cannot upload to a StringIO object"
|
675
729
|
end
|
676
730
|
|
677
|
-
# Fail early. We check the
|
731
|
+
# Fail early. We check whether the StringIO object is available to read
|
678
732
|
files.each do |file|
|
679
733
|
if file.is_a?(StringIO)
|
680
734
|
raise "Cannot download a StringIO object" if direction == :download
|
@@ -694,13 +748,13 @@ module Rye
|
|
694
748
|
self.mkdir(:p, other) unless self.file_exists?(other)
|
695
749
|
end
|
696
750
|
|
697
|
-
Net::SCP.start(@
|
751
|
+
Net::SCP.start(@rye_host, @rye_opts[:user], @rye_opts || {}) do |scp|
|
698
752
|
transfers = []
|
699
753
|
files.each do |file|
|
700
754
|
debug file.to_s
|
701
755
|
transfers << scp.send(direction, file, other) do |ch, n, s, t|
|
702
756
|
pinfo "#{n}: #{s}/#{t}b\r" # update line: "file: sent/total"
|
703
|
-
@
|
757
|
+
@rye_info.flush if @rye_info # make sure every line is printed
|
704
758
|
end
|
705
759
|
end
|
706
760
|
transfers.each { |t| t.wait } # Run file transfers in parallel
|
data/lib/rye/cmd.rb
CHANGED
@@ -98,6 +98,24 @@ module Rye;
|
|
98
98
|
# NOTE: Changes to current working directory with +cd+ or +[]+ are ignored.
|
99
99
|
def download(*files); net_scp_transfer!(:download, *files); end
|
100
100
|
|
101
|
+
|
102
|
+
def file_append(filepath, newcontent, backup=false)
|
103
|
+
if self.file_exists?(filepath) && backup
|
104
|
+
self.cp filepath, "#{filepath}-previous"
|
105
|
+
end
|
106
|
+
|
107
|
+
file_content = self.download filepath
|
108
|
+
file_content ||= StringIO.new
|
109
|
+
if newcontent.is_a?(StringIO)
|
110
|
+
newcontent.rewind
|
111
|
+
file_content.puts newcontent.read
|
112
|
+
else
|
113
|
+
file_content.puts newcontent
|
114
|
+
end
|
115
|
+
|
116
|
+
self.upload file_content, filepath
|
117
|
+
end
|
118
|
+
|
101
119
|
# Does a remote path exist?
|
102
120
|
def file_exists?(path)
|
103
121
|
begin
|
@@ -109,41 +127,6 @@ module Rye;
|
|
109
127
|
# But on OSX exit code is 1. This is why we look at STDERR.
|
110
128
|
ret.stderr.empty?
|
111
129
|
end
|
112
|
-
|
113
|
-
# Return the value of uname in lowercase
|
114
|
-
# This is a temporary fix. We can use SysInfo for this, upload
|
115
|
-
# it, execute it directly, parse the output.
|
116
|
-
def ostype
|
117
|
-
return @ostype if @ostype # simple cache
|
118
|
-
os = self.uname.first rescue nil
|
119
|
-
os ||= 'unknown'
|
120
|
-
os &&= os.downcase
|
121
|
-
@ostype = os
|
122
|
-
end
|
123
|
-
|
124
|
-
# Returns the hash containing the parsed output of "env" on the
|
125
|
-
# remote machine. If the initialize option +:getenv+ was set to
|
126
|
-
# false, this will return an empty hash.
|
127
|
-
# This is a lazy loaded method so it fetches the remote envvars
|
128
|
-
# the first time this method is called.
|
129
|
-
#
|
130
|
-
# puts rbox.getenv['HOME'] # => "/home/gloria" (remote)
|
131
|
-
#
|
132
|
-
# NOTE: This method should not raise an exception under normal
|
133
|
-
# circumstances.
|
134
|
-
#
|
135
|
-
def getenv
|
136
|
-
if @getenv && @getenv.empty? && self.can?(:env)
|
137
|
-
env = self.env rescue []
|
138
|
-
env.each do |nv|
|
139
|
-
# Parse "GLORIA_HOME=/gloria/lives/here" into a name/value
|
140
|
-
# pair. The regexp ensures we split only at the 1st = sign
|
141
|
-
n, v = nv.scan(/\A([\w_-]+?)=(.+)\z/).flatten
|
142
|
-
@getenv[n] = v
|
143
|
-
end
|
144
|
-
end
|
145
|
-
@getenv
|
146
|
-
end
|
147
130
|
|
148
131
|
# Returns an Array of system commands available over SSH
|
149
132
|
def can
|
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.
|
4
|
+
s.version = "0.7.0"
|
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"
|