delano-rye 0.5.4 → 0.6.0
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.
- data/CHANGES.txt +40 -21
- data/README.rdoc +28 -7
- data/bin/rye +1 -3
- data/bin/try +38 -8
- data/lib/rye.rb +22 -2
- data/lib/rye/box.rb +259 -77
- data/lib/rye/cmd.rb +86 -20
- data/lib/rye/rap.rb +23 -2
- data/lib/rye/set.rb +3 -3
- data/lib/sys.rb +13 -0
- data/rye.gemspec +10 -8
- data/tst/{50_rye_test.rb → 50_rset_test.rb} +1 -1
- data/tst/60-file.mp3 +0 -0
- data/tst/60_rbox_transfer_test.rb +53 -0
- data/tst/70_rbox_env_test.rb +19 -0
- metadata +5 -2
data/CHANGES.txt
CHANGED
@@ -5,6 +5,27 @@ TODO
|
|
5
5
|
* Fingerprints: ssh-keygen -l -f id_rsa_repos.pub
|
6
6
|
|
7
7
|
|
8
|
+
#### 0.6.0 (2009-04-28) #############################
|
9
|
+
|
10
|
+
* FIXED: handling of Process::Status ($?) in Rye.shell
|
11
|
+
* FIXED: Removed reference to Rudy::Error in bin/rye
|
12
|
+
* ADDED: Rye::Box.getenv
|
13
|
+
* ADDED: Rye::Box.can?
|
14
|
+
* ADDED: Rye::Box.umask= (a similar work around as cd / [])
|
15
|
+
* ADDED: Rye::Box.file_exists?
|
16
|
+
* ADDED: Rye::Box.authorize_keys_remote can now authorize for a different user
|
17
|
+
* ADDED: Rye::Box.upload and Rye::Box.download
|
18
|
+
* ADDED: Rye::SystemInfo.tmpdir
|
19
|
+
* CHANGE: Rye::Box.prep_args doesn't treat multicharacter Symbols as
|
20
|
+
command-line options any more (single character ones are still converted)
|
21
|
+
* CHANGE: Renamed Rye::Box.add_env to Rye::Box.setenv
|
22
|
+
* CHANGE: rm and kill are available in Rye::Cmd by default
|
23
|
+
* CHANGE: Rye::Box.authorize_keys renamed Rye::Box.authorize_keys_remote
|
24
|
+
* CHANGE: The Rye::Rap object now contains the exit code as an integer for
|
25
|
+
Rye.shell and Rye::Box.run_command (SSH) commands.
|
26
|
+
* UPDATED: Rdocs and README
|
27
|
+
|
28
|
+
|
8
29
|
#### 0.5.4 (2009-04-22) #############################
|
9
30
|
|
10
31
|
* FIXED: Sys is now returning environment paths and home path in JRuby.
|
@@ -19,57 +40,55 @@ TODO
|
|
19
40
|
* ADDED: Rye::Box.connect now rescues HostKeyMismatch exceptions and
|
20
41
|
prompts for a response.
|
21
42
|
|
22
|
-
|
23
43
|
#### 0.5.2 (2009-04-19) #############################
|
24
44
|
|
25
45
|
* FIXED: authorize-local command attempted to connect via SSH before authorizing.
|
26
46
|
|
27
47
|
#### 0.5.0 (2009-04-18) #############################
|
28
48
|
|
29
|
-
* ADDED: Rye::Box.switch_user
|
30
|
-
* ADDED: Several new commands to Rye::Cmd
|
31
|
-
* ADDED: Rye::Box.authorize_keys_local and "rye authorize-local
|
32
49
|
* FIXED: Bug in connect which prevented key-based logins for reconnections
|
33
50
|
* FIXED: Method errors in JRuby
|
34
51
|
* FIXED: Bug in Rye::Set.add_boxes pushing nils into the list of boxes
|
52
|
+
* ADDED: Rye::Box.switch_user
|
53
|
+
* ADDED: Several new commands to Rye::Cmd
|
54
|
+
* ADDED: Rye::Box.authorize_keys_local and "rye authorize-local
|
55
|
+
|
35
56
|
|
36
57
|
#### 0.4.3 (2009-04-14) #############################
|
37
58
|
|
38
|
-
* ADDED: Rye::Box.missing_method to handle non existent commands
|
39
59
|
* FIXED: All Rye::Cmd command methods accept *args to make calling consistent.
|
40
|
-
|
60
|
+
* ADDED: Rye::Box.missing_method to handle non existent commands
|
41
61
|
|
42
62
|
#### 0.4.2 (2009-04-13) #############################
|
43
63
|
|
44
64
|
* ADDED: More helpful debug output
|
45
65
|
* ADDED: hostname command to Rye::Cmd
|
46
|
-
* CHANGE: Using OpenSSL's ssh-agent but also let's Net::SSH handle
|
47
|
-
the ssh keys.
|
48
66
|
* ADDED: Rye::Box.connect now supports multiple password attempts if STDIN.tty returns true
|
49
67
|
* ADDED: Rye::Box.interactive_ssh for opening an SSH session to the given box.
|
68
|
+
* CHANGE: Using OpenSSL's ssh-agent but also let's Net::SSH handle the ssh keys.
|
50
69
|
|
51
70
|
|
52
71
|
#### 0.4.1 (2009-04-06) #############################
|
53
72
|
|
54
|
-
* FIXED: Rye::Box.
|
55
|
-
* ADDED: "rye authorize" now specifically enforces the auth method order
|
73
|
+
* FIXED: Rye::Box.authorize_keys_remote was not disabling safe mode properly
|
56
74
|
* FIXED: Disabled debug mode.
|
75
|
+
* ADDED: "rye authorize" now specifically enforces the auth method order
|
57
76
|
|
58
77
|
|
59
78
|
#### 0.4.0 (2009-04-06) #############################
|
60
79
|
|
61
|
-
* ADDED: to_s and inspect methods for cleaner debugging output
|
62
|
-
* ADDED: == method for Rye::Box
|
63
|
-
* ADDED: exit code and exit signal to Rye::Rap objects
|
64
|
-
* ADDED: commands now raise a Rye::CommandError exception
|
65
|
-
when the command returns an exit code greater than 0.
|
66
|
-
* CHANGED: Box.add_command renamed to Box.run_command
|
67
80
|
* FIXED: Box.run_command was parsing arguments incorrectly
|
68
81
|
* FIXED: Box.net_ssh_exec was working on nil stderr
|
69
82
|
* FIXED: bin/try handles the new command exceptions
|
83
|
+
* ADDED: to_s and inspect methods for cleaner debugging output
|
84
|
+
* ADDED: == method for Rye::Box
|
85
|
+
* ADDED: exit code and exit signal to Rye::Rap objects
|
70
86
|
* ADDED: Command switches can now be sent as Symbols (rbox.ls(:h))
|
71
87
|
* ADDED: Rye.host_keys
|
72
88
|
* ADDED: bin/rye
|
89
|
+
* ADDED: commands now raise a Rye::CommandError exception
|
90
|
+
when the command returns an exit code greater than 0.
|
91
|
+
* CHANGE: Box.add_command renamed to Box.run_command
|
73
92
|
|
74
93
|
|
75
94
|
#### 0.3.2 (2009-04-05) #############################
|
@@ -81,24 +100,24 @@ TODO
|
|
81
100
|
|
82
101
|
#### 0.3 (2009-04-05) ###############################
|
83
102
|
|
103
|
+
* FIXED: Rye::Box wasn't properly adding keypairs to SSH Agent
|
104
|
+
* FIXED: Rye::Box.method_missing Symbol/String ambiguity
|
84
105
|
* ADDED: Rye::Set supports executing commands parallel
|
85
106
|
* ADDED: Rye::Rap now contains STDERR output from command
|
86
|
-
* FIXED: Rye::Box wasn't properly adding keypairs to SSH Agent
|
87
|
-
* CHANGED: Moved all SSH key stuff to Rye (used to be done per Box)
|
88
107
|
* ADDED: Supports all options provided by Net::SSH#start. This
|
89
|
-
includes support for password logins and proxies.
|
108
|
+
includes support for password logins and proxies.
|
90
109
|
* ADDED: Safe mode can now be disabled (to allow file globs
|
91
110
|
and environment variable access).
|
92
111
|
* ADDED: Basic sanity test
|
93
|
-
* FIXED: Rye::Box.method_missing Symbol/String ambiguity
|
94
112
|
* ADDED: Mucho more rdocs and examples.
|
113
|
+
* CHANGE: Moved all SSH key stuff to Rye (used to be done per Box)
|
114
|
+
|
95
115
|
|
96
116
|
#### 0.2 (2009-04-04) ###############################
|
97
117
|
|
98
118
|
* FIXED: ssh-agent shutdown wasn't deleting the SSH tmp directory
|
99
119
|
* ADDED: Now with more rdocs!
|
100
120
|
|
101
|
-
|
102
121
|
#### 0.1 (2009-04-03) ###############################
|
103
122
|
|
104
123
|
Initial public release
|
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Rye - v0.
|
1
|
+
= Rye - v0.6
|
2
2
|
|
3
3
|
Safely run SSH commands on a bunch of machines at the same time (from Ruby).
|
4
4
|
|
@@ -38,7 +38,7 @@ One of:
|
|
38
38
|
puts rbox.cd('/home/rye').pwd # => /home/rye
|
39
39
|
|
40
40
|
# You can specify environment variables
|
41
|
-
rbox.
|
41
|
+
rbox.setenv(:RYE, "Forty Creek")
|
42
42
|
rbox.env # => ['HOME=/home/rye', 'RYE=Forty Creek', ...]
|
43
43
|
|
44
44
|
# The commands method returns an Array of available commands:
|
@@ -123,7 +123,7 @@ One of:
|
|
123
123
|
puts unames.first.box == rbox # => true
|
124
124
|
|
125
125
|
# Envrionment variables can be set the same way as with Rye::Box
|
126
|
-
rset.
|
126
|
+
rset.setenv(:RYE, "Forty Creek")
|
127
127
|
p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"]
|
128
128
|
|
129
129
|
|
@@ -152,20 +152,41 @@ One of:
|
|
152
152
|
# There were no actual error so the exit code should be 0.
|
153
153
|
puts rbox.uname('-a 1>&2').exit_code # => 0
|
154
154
|
|
155
|
-
|
155
|
+
|
156
|
+
== EXAMPLE 6 -- FILE TRANSFERS
|
157
|
+
|
158
|
+
rbox = Rye::Box.new("localhost", :info => true)
|
159
|
+
|
160
|
+
dir_upload = "#{Rye.sysinfo.tmpdir}/rye-upload/"
|
161
|
+
dir_download = "#{Rye.sysinfo.tmpdir}/rye-download/"
|
162
|
+
|
163
|
+
rbox.upload("#{RYE_HOME}/README.rdoc",
|
164
|
+
"#{RYE_HOME}/LICENSE.txt", dir_upload)
|
165
|
+
|
166
|
+
applejack = StringIO.new("Some in-memory content")
|
167
|
+
rbox.upload(applejack, "#{dir_upload}/applejack.txt")
|
168
|
+
|
169
|
+
p rbox.ls(dir_upload) # => [README.rdoc, LICENSE.txt, applejack.txt]
|
170
|
+
p rbox.cat("#{dir_upload}/applejack.txt") # => "Some in-memory content"
|
171
|
+
|
172
|
+
filecontent = StringIO.new
|
173
|
+
rbox.download("#{dir_upload}/applejack.txt", filecontent)
|
174
|
+
|
175
|
+
p filecontent.read
|
176
|
+
|
177
|
+
== EXAMPLE 7 -- rye
|
156
178
|
|
157
179
|
Rye comes with a command-line utility called <tt>rye</tt>.
|
158
180
|
|
159
181
|
# Prints the paths to your private keys
|
160
182
|
$ rye keys
|
161
|
-
|
183
|
+
|
162
184
|
# Prints the server host keys (suitable for ~/.ssh/known_hosts)
|
163
185
|
$ rye hostkey HOST1 HOST2
|
164
|
-
|
186
|
+
|
165
187
|
# Add your public keys to authorized_keys and authorized_keys2
|
166
188
|
# on a remote machine
|
167
189
|
$ rye authorize HOST1 HOST2
|
168
|
-
|
169
190
|
More info:
|
170
191
|
|
171
192
|
$ rye -h
|
data/bin/rye
CHANGED
@@ -48,7 +48,7 @@ command :authorize do |obj|
|
|
48
48
|
|
49
49
|
puts "Authorizing #{rbox.opts[:user]}@#{hostname}"
|
50
50
|
rbox = Rye::Box.new(hostname, opts).connect
|
51
|
-
puts "Added public keys for: ", rbox.
|
51
|
+
puts "Added public keys for: ", rbox.authorize_keys_remote
|
52
52
|
puts "Now try: " << "ssh #{rbox.opts[:user]}@#{hostname}"
|
53
53
|
|
54
54
|
end
|
@@ -118,8 +118,6 @@ begin
|
|
118
118
|
rescue Drydock::ArgError, Drydock::OptError=> ex
|
119
119
|
STDERR.puts ex.message
|
120
120
|
STDERR.puts ex.usage
|
121
|
-
rescue Rudy::Error => ex
|
122
|
-
STDERR.puts ex.message
|
123
121
|
rescue => ex
|
124
122
|
STDERR.puts "ERROR (#{ex.class.to_s}): #{ex.message}"
|
125
123
|
STDERR.puts ex.backtrace if Drydock.debug?
|
data/bin/try
CHANGED
@@ -8,7 +8,8 @@
|
|
8
8
|
# Usage: bin/try
|
9
9
|
#
|
10
10
|
|
11
|
-
|
11
|
+
RYE_HOME = File.join(File.dirname(__FILE__), '..')
|
12
|
+
$:.unshift File.join(RYE_HOME, 'lib')
|
12
13
|
|
13
14
|
require 'stringio'
|
14
15
|
require 'yaml'
|
@@ -34,10 +35,9 @@ p rbox.ls(:a, '/') # => ['.', '..', 'bin', 'etc', ...]
|
|
34
35
|
puts rbox.pwd # => /home/rye
|
35
36
|
puts rbox['/usr/bin'].pwd # => /usr/bin
|
36
37
|
puts rbox.pwd # => /usr/bin
|
37
|
-
puts rbox.cd(Rye.sysinfo.home).pwd # => /home/rye
|
38
38
|
|
39
39
|
# You can specify environment variables
|
40
|
-
rbox.
|
40
|
+
rbox.setenv(:RYE, "Forty Creek")
|
41
41
|
rbox.env # => ['HOME=/home/rye', 'RYE=Forty Creek', ...]
|
42
42
|
|
43
43
|
# The commands method returns an Array of available commands:
|
@@ -133,7 +133,7 @@ puts unames.first.box.class # => Rye::Box
|
|
133
133
|
puts unames.first.box == rbox # => true
|
134
134
|
|
135
135
|
# Envrionment variables can be set the same way as with Rye::Box
|
136
|
-
rset.
|
136
|
+
rset.setenv(:RYE, "Forty Creek")
|
137
137
|
p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"]
|
138
138
|
|
139
139
|
|
@@ -142,7 +142,7 @@ puts %Q(
|
|
142
142
|
# EXAMPLE 5 -- ERROR HANDLING
|
143
143
|
#)
|
144
144
|
|
145
|
-
rbox = Rye::Box.new('localhost', :safe => false)
|
145
|
+
rbox = Rye::Box.new('localhost', :safe => false) # Note: safe mode is off
|
146
146
|
|
147
147
|
# Rye follows the standard convention of taking exception to a non-zero
|
148
148
|
# exit code by raising a Rye::CommandError. In this case, rye9000.test
|
@@ -150,8 +150,8 @@ rbox = Rye::Box.new('localhost', :safe => false)
|
|
150
150
|
begin
|
151
151
|
rbox.ls('rye.test')
|
152
152
|
rescue Rye::CommandError => ex
|
153
|
-
puts ex.exit_code
|
154
|
-
puts ex.stderr
|
153
|
+
puts ex.exit_code # => 1
|
154
|
+
puts ex.stderr # => ls: rye.test: No such file or directory
|
155
155
|
end
|
156
156
|
|
157
157
|
# The Rye:Rap response objects also give you the STDOUT and STDERR
|
@@ -162,10 +162,40 @@ puts rbox.uname('-a 1>&2').stdout # =>
|
|
162
162
|
# It all went to STDERR:
|
163
163
|
puts rbox.uname('-a 1>&2').stderr # => Darwin ryehost 9.6.0 ...
|
164
164
|
|
165
|
-
# There were no actual
|
165
|
+
# There were no actual errors so the exit code should be 0.
|
166
166
|
puts rbox.uname('-a 1>&2').exit_code # => 0
|
167
167
|
|
168
168
|
|
169
|
+
puts %Q(
|
170
|
+
# ------------------------------------------------------------------
|
171
|
+
# EXAMPLE 6 -- FILE TRANSFERS
|
172
|
+
#)
|
173
|
+
|
174
|
+
dir_upload = "#{Rye.sysinfo.tmpdir}/rye-upload/"
|
175
|
+
dir_download = "#{Rye.sysinfo.tmpdir}/rye-download/"
|
176
|
+
|
177
|
+
rbox = Rye::Box.new("localhost", :info => false)
|
178
|
+
|
179
|
+
# Rye ships without an rm method (for safety!). Here
|
180
|
+
# we add the rm method only to this instance of rbox.
|
181
|
+
def rbox.rm(*args); cmd('rm', args); end
|
182
|
+
|
183
|
+
rbox.rm(:r, :f, dir_upload) # Silently delete test dirs
|
184
|
+
rbox.rm(:r, :f, dir_download)
|
185
|
+
|
186
|
+
rbox.upload("#{RYE_HOME}/README.rdoc",
|
187
|
+
"#{RYE_HOME}/LICENSE.txt", dir_upload)
|
188
|
+
|
189
|
+
applejack = StringIO.new("Some in-memory content")
|
190
|
+
rbox.upload(applejack, "#{dir_upload}/applejack.txt")
|
191
|
+
|
192
|
+
p rbox.ls(dir_upload) # => [README.rdoc, LICENSE.txt, applejack.txt]
|
193
|
+
p rbox.cat("#{dir_upload}/applejack.txt") # => "Some in-memory content"
|
194
|
+
|
195
|
+
filecontent = StringIO.new
|
196
|
+
rbox.download("#{dir_upload}/applejack.txt", filecontent)
|
197
|
+
|
198
|
+
p filecontent.read
|
169
199
|
|
170
200
|
|
171
201
|
|
data/lib/rye.rb
CHANGED
@@ -36,7 +36,7 @@ module Rye
|
|
36
36
|
extend self
|
37
37
|
|
38
38
|
unless defined?(SYSINFO)
|
39
|
-
VERSION = 0.
|
39
|
+
VERSION = 0.6.freeze
|
40
40
|
SYSINFO = SystemInfo.new.freeze
|
41
41
|
end
|
42
42
|
|
@@ -54,6 +54,7 @@ module Rye
|
|
54
54
|
class NotConnected < RuntimeError; end
|
55
55
|
class CommandNotFound < RuntimeError; end
|
56
56
|
class CommandError < RuntimeError
|
57
|
+
attr_reader :rap
|
57
58
|
# * +rap+ a Rye::Rap object
|
58
59
|
def initialize(rap)
|
59
60
|
@rap = rap
|
@@ -208,7 +209,7 @@ module Rye
|
|
208
209
|
rap = Rye::Rap.new(self)
|
209
210
|
rap.add_stdout(stdout || '')
|
210
211
|
rap.add_stderr(stderr || '')
|
211
|
-
rap.
|
212
|
+
rap.add_exit_code($?)
|
212
213
|
rap
|
213
214
|
end
|
214
215
|
|
@@ -224,6 +225,25 @@ module Rye
|
|
224
225
|
@@agent_env
|
225
226
|
end
|
226
227
|
|
228
|
+
# Returns +str+ with the leading indentation removed.
|
229
|
+
# Stolen from http://github.com/mynyml/unindent/ because it was better.
|
230
|
+
def without_indent(str)
|
231
|
+
indent = str.split($/).each {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min
|
232
|
+
str.gsub(/^[[:blank:]]{#{indent}}/, '')
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# Generates a string of random alphanumeric characters.
|
237
|
+
# * +len+ is the length, an Integer. Default: 8
|
238
|
+
# * +safe+ in safe-mode, ambiguous characters are removed (default: true):
|
239
|
+
# i l o 1 0
|
240
|
+
def strand( len=8, safe=true )
|
241
|
+
chars = ("a".."z").to_a + ("0".."9").to_a
|
242
|
+
chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
|
243
|
+
str = ""
|
244
|
+
1.upto(len) { |i| str << chars[rand(chars.size-1)] }
|
245
|
+
str
|
246
|
+
end
|
227
247
|
|
228
248
|
private
|
229
249
|
|
data/lib/rye/box.rb
CHANGED
@@ -19,22 +19,32 @@ module Rye
|
|
19
19
|
# rbox.hostname # => localhost
|
20
20
|
# rbox.uname(:a) # => Darwin vanya 9.6.0 ...
|
21
21
|
#
|
22
|
+
#--
|
23
|
+
# * When anything confusing happens, enable debug in initialize
|
24
|
+
# by passing :debug => STDERR. This will output Rye debug info
|
25
|
+
# as well as Net::SSH info. This is VERY helpful for figuring
|
26
|
+
# out why some command is hanging or otherwise acting weird.
|
27
|
+
# * If a remote command is hanging, it's probably because a
|
28
|
+
# Net::SSH channel is waiting on_extended_data (a prompt).
|
29
|
+
#++
|
22
30
|
class Box
|
23
31
|
include Rye::Cmd
|
24
32
|
|
25
33
|
# An instance of Net::SSH::Connection::Session
|
26
34
|
attr_reader :ssh
|
27
35
|
|
36
|
+
attr_reader :info
|
28
37
|
attr_reader :debug
|
29
38
|
attr_reader :error
|
30
39
|
|
31
40
|
attr_accessor :host
|
32
|
-
|
33
41
|
attr_accessor :safe
|
34
42
|
attr_accessor :opts
|
35
|
-
|
43
|
+
|
36
44
|
# The most recent value from Box.cd or Box.[]
|
37
45
|
attr_reader :current_working_directory
|
46
|
+
# The most recent valud for umask (or 0022)
|
47
|
+
attr_reader :current_umask
|
38
48
|
|
39
49
|
# * +host+ The hostname to connect to. The default is localhost.
|
40
50
|
# * +opts+ a hash of optional arguments.
|
@@ -44,14 +54,17 @@ module Rye
|
|
44
54
|
# * :user => the username to connect as. Default: the current user.
|
45
55
|
# * :safe => should Rye be safe? Default: true
|
46
56
|
# * :keys => one or more private key file paths (passwordless login)
|
47
|
-
# * :
|
57
|
+
# * :info => an IO object to print Rye::Box command info to. Default: nil
|
48
58
|
# * :debug => an IO object to print Rye::Box debugging info to. Default: nil
|
49
59
|
# * :error => an IO object to print Rye::Box errors to. Default: STDERR
|
60
|
+
# * :getenv => pre-fetch +host+ environment variables? (default: true)
|
61
|
+
# * :password => the user's password (ignored if there's a valid private key)
|
50
62
|
#
|
51
63
|
# NOTE: +opts+ can also contain any parameter supported by
|
52
64
|
# Net::SSH.start that is not already mentioned above.
|
53
65
|
#
|
54
66
|
def initialize(host='localhost', opts={})
|
67
|
+
@host = host
|
55
68
|
|
56
69
|
# These opts are use by Rye::Box and also passed to Net::SSH
|
57
70
|
@opts = {
|
@@ -59,48 +72,46 @@ module Rye
|
|
59
72
|
:safe => true,
|
60
73
|
:port => 22,
|
61
74
|
:keys => [],
|
75
|
+
:info => nil,
|
62
76
|
:debug => nil,
|
63
77
|
:error => STDERR,
|
78
|
+
:getenv => true,
|
64
79
|
}.merge(opts)
|
65
80
|
|
66
|
-
# See Net::SSH.start
|
67
|
-
@opts[:paranoid] = true unless @opts[:safe] == false
|
68
|
-
|
69
81
|
# Close the SSH session before Ruby exits. This will do nothing
|
70
82
|
# if disconnect has already been called explicitly.
|
71
|
-
at_exit {
|
72
|
-
self.disconnect
|
73
|
-
}
|
74
|
-
|
75
|
-
@host = host
|
83
|
+
at_exit { self.disconnect }
|
76
84
|
|
77
|
-
@
|
78
|
-
|
79
|
-
@
|
85
|
+
# @opts gets sent to Net::SSH so we need to remove the keys
|
86
|
+
# that are not meant for it.
|
87
|
+
@safe, @debug = @opts.delete(:safe), @opts.delete(:debug)
|
88
|
+
@info, @error = @opts.delete(:info), @opts.delete(:error)
|
89
|
+
@getenv = {} if @opts.delete(:getenv) # Enable getenv with a hash
|
80
90
|
|
81
|
-
|
82
|
-
|
91
|
+
# Just in case someone sends a true value rather than IO object
|
92
|
+
@debug = STDERR if @debug == true
|
93
|
+
@error = STDERR if @error == true
|
94
|
+
@info = STDOUT if @info == true
|
95
|
+
|
96
|
+
@opts[:logger] = Logger.new(@debug) if @debug # Enable Net::SSH debugging
|
97
|
+
@opts[:paranoid] = true unless @opts[:safe] == false # See Net::SSH.start
|
98
|
+
|
99
|
+
# Add the given private keys to the keychain that will be used for @host
|
83
100
|
add_keys(@opts[:keys])
|
84
101
|
|
85
102
|
# We don't want Net::SSH to handle the keypairs. This may change
|
86
103
|
# but for we're letting ssh-agent do it.
|
87
104
|
#@opts.delete(:keys)
|
88
105
|
|
89
|
-
|
90
|
-
|
106
|
+
# From: capistrano/lib/capistrano/cli.rb
|
107
|
+
STDOUT.sync = true # so that Net::SSH prompts show up
|
91
108
|
|
109
|
+
debug "ssh-agent info: #{Rye.sshagent_info.inspect}"
|
110
|
+
debug @opts.inspect
|
111
|
+
|
92
112
|
end
|
93
113
|
|
94
|
-
|
95
|
-
# Returns an Array of system commands available over SSH
|
96
|
-
def can
|
97
|
-
Rye::Cmd.instance_methods
|
98
|
-
end
|
99
|
-
alias :commands :can
|
100
|
-
alias :cmds :can
|
101
114
|
|
102
|
-
|
103
|
-
|
104
115
|
# Change the current working directory (sort of).
|
105
116
|
#
|
106
117
|
# I haven't been able to wrangle Net::SSH to do my bidding.
|
@@ -123,19 +134,33 @@ module Rye
|
|
123
134
|
@current_working_directory = key
|
124
135
|
self
|
125
136
|
end
|
126
|
-
#
|
127
|
-
|
137
|
+
# Like [] except it returns an empty Rye::Rap object to mimick
|
138
|
+
# a regular command method. Call with nil key (or no arg) to
|
139
|
+
# reset.
|
140
|
+
def cd(key=nil)
|
128
141
|
@current_working_directory = key
|
142
|
+
ret = Rye::Rap.new(self)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Change the current umask (sort of -- works the same way as cd)
|
146
|
+
# The default umask is 0022
|
147
|
+
def umask=(val='0022')
|
148
|
+
@current_umask = val
|
129
149
|
self
|
130
150
|
end
|
131
|
-
|
151
|
+
|
152
|
+
|
132
153
|
# Open an SSH session with +@host+. This called automatically
|
133
154
|
# when you the first comamnd is run if it's not already connected.
|
134
155
|
# Raises a Rye::NoHost exception if +@host+ is not specified.
|
135
156
|
# Will attempt a password login up to 3 times if the initial
|
136
157
|
# authentication fails.
|
137
|
-
|
158
|
+
# * +reconnect+ Disconnect first if already connected. The default
|
159
|
+
# is true. When set to false, connect will do nothing if already
|
160
|
+
# connected.
|
161
|
+
def connect(reconnect=true)
|
138
162
|
raise Rye::NoHost unless @host
|
163
|
+
return if @ssh && !reconnect
|
139
164
|
disconnect if @ssh
|
140
165
|
debug "Opening connection to #{@host} as #{@opts[:user]}"
|
141
166
|
highline = HighLine.new # Used for password prompt
|
@@ -146,7 +171,7 @@ module Rye
|
|
146
171
|
rescue Net::SSH::HostKeyMismatch => ex
|
147
172
|
STDERR.puts ex.message
|
148
173
|
STDERR.puts "NOTE: EC2 instances generate new SSH keys on first boot."
|
149
|
-
print "\a" # Ring the bell
|
174
|
+
print "\a" if @info # Ring the bell
|
150
175
|
if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i)
|
151
176
|
@opts[:paranoid] = false
|
152
177
|
retry
|
@@ -154,6 +179,7 @@ module Rye
|
|
154
179
|
raise Net::SSH::HostKeyMismatch
|
155
180
|
end
|
156
181
|
rescue Net::SSH::AuthenticationFailed => ex
|
182
|
+
print "\a" if retried == 0 && @info # Ring the bell once
|
157
183
|
retried += 1
|
158
184
|
if STDIN.tty? && retried <= 3
|
159
185
|
@opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
|
@@ -171,20 +197,11 @@ module Rye
|
|
171
197
|
# the next connection (if we switch_user is called for example).
|
172
198
|
@opts.delete :auth_methods if @opts.has_key?(:auth_methods)
|
173
199
|
|
174
|
-
@ssh.is_a?(Net::SSH::Connection::Session) && !@ssh.closed?
|
175
200
|
self
|
176
201
|
end
|
177
202
|
|
178
|
-
#
|
179
|
-
#
|
180
|
-
def disconnect
|
181
|
-
return unless @ssh && !@ssh.closed?
|
182
|
-
@ssh.loop(0.1) { @ssh.busy? }
|
183
|
-
debug "Closing connection to #{@ssh.host}"
|
184
|
-
@ssh.close
|
185
|
-
end
|
186
|
-
|
187
|
-
# Reconnect as another user
|
203
|
+
# Reconnect as another user. This is different from su=
|
204
|
+
# which executes subsequent commands via +su -c COMMAND USER+.
|
188
205
|
# * +newuser+ The username to reconnect as
|
189
206
|
#
|
190
207
|
# NOTE: if there is an open connection, it's disconnected
|
@@ -197,6 +214,17 @@ module Rye
|
|
197
214
|
connect
|
198
215
|
end
|
199
216
|
|
217
|
+
|
218
|
+
# Close the SSH session with +@host+. This is called
|
219
|
+
# automatically at exit if the connection is open.
|
220
|
+
def disconnect
|
221
|
+
return unless @ssh && !@ssh.closed?
|
222
|
+
@ssh.loop(0.1) { @ssh.busy? }
|
223
|
+
debug "Closing connection to #{@ssh.host}"
|
224
|
+
@ssh.close
|
225
|
+
end
|
226
|
+
|
227
|
+
|
200
228
|
# Open an interactive SSH session. This only works if STDIN.tty?
|
201
229
|
# returns true. Otherwise it returns the SSH command that would
|
202
230
|
# have been run. This requires the SSH command-line executable (ssh).
|
@@ -229,12 +257,14 @@ module Rye
|
|
229
257
|
|
230
258
|
# Add an environment variable. +n+ and +v+ are the name and value.
|
231
259
|
# Returns the instance of Rye::Box
|
232
|
-
def
|
233
|
-
debug "
|
260
|
+
def setenv(n, v)
|
261
|
+
debug "Adding env: #{n}=#{v}"
|
262
|
+
debug "prev value: #{@getenv[n]}"
|
263
|
+
@getenv[n] = v
|
234
264
|
(@current_environment_variables ||= {})[n] = v
|
235
265
|
self
|
236
266
|
end
|
237
|
-
alias :
|
267
|
+
alias :add_env :setenv # deprecated?
|
238
268
|
|
239
269
|
def user
|
240
270
|
(@opts || {})[:user]
|
@@ -251,9 +281,10 @@ module Rye
|
|
251
281
|
end
|
252
282
|
|
253
283
|
def inspect
|
254
|
-
%q{#<%s:%s cwd=%s env=%s safe=%s opts=%s>} %
|
284
|
+
%q{#<%s:%s cwd=%s umask=%s env=%s safe=%s opts=%s>} %
|
255
285
|
[self.class.to_s, self.host,
|
256
|
-
@current_working_directory,
|
286
|
+
@current_working_directory, @current_umask,
|
287
|
+
(@current_environment_variables || '').inspect,
|
257
288
|
self.safe, self.opts.inspect]
|
258
289
|
end
|
259
290
|
|
@@ -269,34 +300,89 @@ module Rye
|
|
269
300
|
Rye.remote_host_keys(@host)
|
270
301
|
end
|
271
302
|
|
303
|
+
# Uses the output of "useradd -D" to determine the default home
|
304
|
+
# directory. This returns a GUESS rather than the a user's real
|
305
|
+
# home directory. Currently used only by authorize_keys_remote.
|
306
|
+
def guess_user_home(other_user=nil)
|
307
|
+
# Some junk to determine where user home directories are by default.
|
308
|
+
# We're relying on the command "useradd -D" so this may not work on
|
309
|
+
# different Linuxen and definitely won't work on Windows.
|
310
|
+
# This code will be abstracted out once I find a decent home for it.
|
311
|
+
# /etc/default/useradd, HOME=/home OR useradd -D
|
312
|
+
# /etc/adduser.config, DHOME=/home OR ??
|
313
|
+
user_defaults = {}
|
314
|
+
raw = self.useradd(:D) rescue ["HOME=/home"]
|
315
|
+
raw.each do |nv|
|
316
|
+
n, v = nv.scan(/\A([\w_-]+?)=(.+)\z/).flatten
|
317
|
+
user_defaults[n] = v
|
318
|
+
end
|
319
|
+
"#{user_defaults['HOME']}/#{other_user}"
|
320
|
+
end
|
321
|
+
|
272
322
|
# Copy the local public keys (as specified by Rye.keys) to
|
273
323
|
# this box into ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2.
|
274
|
-
# Returns
|
275
|
-
#
|
276
|
-
#
|
324
|
+
# Returns a Rye::Rap object. The private keys files used to generate
|
325
|
+
# the public keys are contained in stdout.
|
326
|
+
# Raises a Rye::ComandError if the home directory doesn't exit.
|
327
|
+
# NOTE: authorize_keys_remote disables safe-mode for this box while it runs
|
277
328
|
# which will hit you funky style if your using a single instance
|
278
329
|
# of Rye::Box in a multithreaded situation.
|
279
330
|
#
|
280
|
-
def
|
331
|
+
def authorize_keys_remote(other_user=nil)
|
332
|
+
this_user = other_user || @user
|
281
333
|
added_keys = []
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
334
|
+
rap = Rye::Rap.new(self)
|
335
|
+
|
336
|
+
# The homedir path is important b/c this is where we're going to
|
337
|
+
# look for the .ssh directory. That's where auth love is stored.
|
338
|
+
homedir = self.guess_user_home(this_user)
|
339
|
+
|
340
|
+
unless self.file_exists?(homedir)
|
341
|
+
rap.add_exit_code(1)
|
342
|
+
rap.add_stderr("Path does not exist: #{homedir}")
|
343
|
+
raise Rye::CommandError.new(rap)
|
292
344
|
end
|
293
|
-
|
294
|
-
|
345
|
+
|
346
|
+
# Let's go into the user's home directory that we now know exists.
|
347
|
+
self.cd homedir
|
348
|
+
|
349
|
+
files = ['.ssh/authorized_keys', '.ssh/authorized_keys2']
|
350
|
+
files.each do |akey_path|
|
351
|
+
if self.file_exists?(akey_path)
|
352
|
+
# TODO: Make Rye::Cmd.incremental_backup
|
353
|
+
self.cp(akey_path, "#{akey_path}-previous")
|
354
|
+
authorized_keys = self.download("#{homedir}/#{akey_path}")
|
355
|
+
end
|
356
|
+
authorized_keys ||= StringIO.new
|
357
|
+
|
358
|
+
Rye.keys.each do |key|
|
359
|
+
path = key[2]
|
360
|
+
info "# Adding public key for #{path}"
|
361
|
+
k = Rye::Key.from_file(path).public_key.to_ssh2
|
362
|
+
authorized_keys.puts k
|
363
|
+
end
|
364
|
+
|
365
|
+
# Remove duplicate authorized keys
|
366
|
+
authorized_keys.rewind
|
367
|
+
uniqlines = authorized_keys.readlines.uniq.join
|
368
|
+
authorized_keys = StringIO.new(uniqlines)
|
369
|
+
# We need to rewind so that all of the StringIO object is uploaded
|
370
|
+
authorized_keys.rewind
|
371
|
+
|
372
|
+
self.mkdir(:p, :m, '700', File.dirname(akey_path))
|
373
|
+
self.upload(authorized_keys, "#{homedir}/#{akey_path}")
|
374
|
+
self.chmod('0600', akey_path)
|
375
|
+
self.chown(:R, this_user.to_s, File.dirname(akey_path))
|
376
|
+
end
|
377
|
+
|
378
|
+
|
379
|
+
rap.add_exit_code(0)
|
380
|
+
rap
|
295
381
|
end
|
296
382
|
|
297
383
|
# Authorize the current user to login to the local machine via
|
298
384
|
# SSH without a password. This is the same functionality as
|
299
|
-
#
|
385
|
+
# authorize_keys_remote except run with local shell commands.
|
300
386
|
def authorize_keys_local
|
301
387
|
added_keys = []
|
302
388
|
Rye.keys.each do |key|
|
@@ -323,10 +409,10 @@ module Rye
|
|
323
409
|
|
324
410
|
private
|
325
411
|
|
326
|
-
|
327
412
|
def debug(msg="unknown debug msg"); @debug.puts msg if @debug; end
|
328
413
|
def error(msg="unknown error msg"); @error.puts msg if @error; end
|
329
|
-
|
414
|
+
def pinfo(msg="unknown info msg"); @info.print msg if @info; end
|
415
|
+
def info(msg="unknown info msg"); @info.puts msg if @info; end
|
330
416
|
|
331
417
|
# Add the current environment variables to the beginning of +cmd+
|
332
418
|
def prepend_env(cmd)
|
@@ -348,7 +434,7 @@ module Rye
|
|
348
434
|
# will be thoroughly escaped and passed to the command.
|
349
435
|
#
|
350
436
|
# rbox = Rye::Box.new
|
351
|
-
# rbox.ls
|
437
|
+
# rbox.ls :l, 'arg1', 'arg2'
|
352
438
|
#
|
353
439
|
# is equivalent to
|
354
440
|
#
|
@@ -367,17 +453,30 @@ module Rye
|
|
367
453
|
|
368
454
|
cmd_clean = Rye.escape(@safe, cmd, args)
|
369
455
|
cmd_clean = prepend_env(cmd_clean)
|
456
|
+
|
457
|
+
# Add the current working directory before the command if supplied.
|
458
|
+
# The command will otherwise run in the user's home directory.
|
370
459
|
if @current_working_directory
|
371
460
|
cwd = Rye.escape(@safe, 'cd', @current_working_directory)
|
372
461
|
cmd_clean = [cwd, cmd_clean].join(' && ')
|
373
462
|
end
|
374
463
|
|
464
|
+
# ditto (same explanation as cwd)
|
465
|
+
if @current_umask
|
466
|
+
cwd = Rye.escape(@safe, 'umask', @current_umask)
|
467
|
+
cmd_clean = [cwd, cmd_clean].join(' && ')
|
468
|
+
end
|
469
|
+
|
470
|
+
|
471
|
+
info "COMMAND: #{cmd_clean}"
|
375
472
|
debug "Executing: %s" % cmd_clean
|
376
|
-
|
473
|
+
|
474
|
+
stdout, stderr, ecode, esignal = net_ssh_exec!(cmd_clean)
|
475
|
+
|
377
476
|
rap = Rye::Rap.new(self)
|
378
477
|
rap.add_stdout(stdout || '')
|
379
478
|
rap.add_stderr(stderr || '')
|
380
|
-
rap.
|
479
|
+
rap.add_exit_code(ecode)
|
381
480
|
rap.exit_signal = esignal
|
382
481
|
rap.cmd = cmd
|
383
482
|
|
@@ -387,10 +486,10 @@ module Rye
|
|
387
486
|
end
|
388
487
|
alias :cmd :run_command
|
389
488
|
|
390
|
-
|
391
|
-
|
392
489
|
# Takes a list of arguments appropriate for run_command or
|
393
|
-
# preview_command and returns: [cmd, args]
|
490
|
+
# preview_command and returns: [cmd, args].
|
491
|
+
# Single character symbols with be converted to command line
|
492
|
+
# switches. Example: +:l+ becomes +-l+
|
394
493
|
def prep_args(*args)
|
395
494
|
args = args.flatten.compact
|
396
495
|
args = args.first.to_s.split(/\s+/) if args.size == 1
|
@@ -398,8 +497,9 @@ module Rye
|
|
398
497
|
|
399
498
|
# Symbols to switches. :l -> -l, :help -> --help
|
400
499
|
args.collect! do |a|
|
401
|
-
|
402
|
-
|
500
|
+
if a.is_a?(Symbol)
|
501
|
+
a = (a.to_s.size == 1) ? "-#{a}" : a.to_s
|
502
|
+
end
|
403
503
|
a
|
404
504
|
end
|
405
505
|
[cmd, args]
|
@@ -408,9 +508,11 @@ module Rye
|
|
408
508
|
# Executes +command+ via SSH
|
409
509
|
# Returns an Array with 4 elements: [stdout, stderr, exit code, exit signal]
|
410
510
|
def net_ssh_exec!(command)
|
511
|
+
|
411
512
|
block ||= Proc.new do |channel, type, data|
|
412
513
|
channel[:stdout] ||= ""
|
413
514
|
channel[:stderr] ||= ""
|
515
|
+
channel[:exit_code] ||= -1
|
414
516
|
channel[:stdout] << data if type == :stdout
|
415
517
|
channel[:stderr] << data if type == :stderr
|
416
518
|
channel.on_request("exit-status") do |ch, data|
|
@@ -426,14 +528,19 @@ module Rye
|
|
426
528
|
# command.
|
427
529
|
#channel.on_data do |ch, data|
|
428
530
|
# puts "got stdout: #{data}"
|
429
|
-
# channel.send_data "something for stdin\n"
|
531
|
+
# #channel.send_data "something for stdin\n"
|
532
|
+
#end
|
533
|
+
#
|
534
|
+
#channel.on_extended_data do |ch, data|
|
535
|
+
# #puts "got stdout: #{data}"
|
536
|
+
# #channel.send_data "something for stdin\n"
|
430
537
|
#end
|
431
538
|
end
|
432
539
|
|
433
540
|
channel = @ssh.exec(command, &block)
|
434
541
|
channel.wait # block until we get a response
|
435
542
|
|
436
|
-
channel[:exit_code]
|
543
|
+
channel[:exit_code] = 0 if channel[:exit_code] == nil
|
437
544
|
channel[:exit_code] &&= channel[:exit_code].to_i
|
438
545
|
|
439
546
|
channel[:stderr].gsub!(/bash: line \d+:\s+/, '') if channel[:stderr]
|
@@ -442,6 +549,81 @@ module Rye
|
|
442
549
|
end
|
443
550
|
|
444
551
|
|
552
|
+
# * +direction+ is one of :upload, :download
|
553
|
+
# * +files+ is an Array of file paths, the content is direction specific.
|
554
|
+
# For downloads, +files+ is a list of files to download. The last element
|
555
|
+
# must be the local directory to download to. If downloading a single file
|
556
|
+
# the last element can be a file path. The target can also be a StringIO.
|
557
|
+
# For uploads, +files+ is a list of files to upload. The last element is
|
558
|
+
# the directory to upload to. If uploading a single file, the last element
|
559
|
+
# can be a file path. The list of files can also include StringIO objects.
|
560
|
+
# For both uploads and downloads, the target directory will be created if
|
561
|
+
# it does not exist, but only when multiple files are being transferred.
|
562
|
+
# This method will fail early if there are obvious problems with the input
|
563
|
+
# parameters. An exception is raised and no files are transferred.
|
564
|
+
# Uploads always return nil. Downloads return nil or a StringIO object if
|
565
|
+
# one is specified for the target.
|
566
|
+
def net_scp_transfer!(direction, *files)
|
567
|
+
direction ||= ''
|
568
|
+
unless [:upload, :download].member?(direction.to_sym)
|
569
|
+
raise "Must be one of: upload, download"
|
570
|
+
end
|
571
|
+
|
572
|
+
if @current_working_directory
|
573
|
+
info "CWD (#{@current_working_directory}) not used"
|
574
|
+
end
|
575
|
+
|
576
|
+
files = [files].flatten.compact || []
|
577
|
+
|
578
|
+
# We allow a single file to be downloaded into a StringIO object
|
579
|
+
# but only when no target has been specified.
|
580
|
+
if direction == :download && files.size == 1
|
581
|
+
debug "Created StringIO for download"
|
582
|
+
other = StringIO.new
|
583
|
+
else
|
584
|
+
other = files.pop
|
585
|
+
end
|
586
|
+
|
587
|
+
if direction == :upload && other.is_a?(StringIO)
|
588
|
+
raise "Cannot upload to a StringIO object"
|
589
|
+
end
|
590
|
+
|
591
|
+
# Fail early. We check the
|
592
|
+
files.each do |file|
|
593
|
+
if file.is_a?(StringIO)
|
594
|
+
raise "Cannot download a StringIO object" if direction == :download
|
595
|
+
raise "StringIO object not opened for reading" if file.closed_read?
|
596
|
+
# If a StringIO object is at end of file, SCP will hang. (TODO: SCP)
|
597
|
+
file.rewind if file.eof?
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
debug "#{direction.to_s.upcase} TO: #{other}"
|
602
|
+
debug "FILES: " << files.join(', ')
|
603
|
+
|
604
|
+
# Make sure the remote directory exists. We can do this only when
|
605
|
+
# there's more than one file because "other" could be a file name
|
606
|
+
if files.size > 1 && !other.is_a?(StringIO)
|
607
|
+
debug "CREATING TARGET DIRECTORY: #{other}"
|
608
|
+
self.mkdir(:p, other) unless self.file_exists?(other)
|
609
|
+
end
|
610
|
+
|
611
|
+
Net::SCP.start(@host, @opts[:user], @opts || {}) do |scp|
|
612
|
+
transfers = []
|
613
|
+
files.each do |file|
|
614
|
+
debug file.to_s
|
615
|
+
transfers << scp.send(direction, file, other) do |ch, n, s, t|
|
616
|
+
pinfo "#{n}: #{s}/#{t}b\r" # update line: "file: sent/total"
|
617
|
+
@info.flush if @info # make sure every line is printed
|
618
|
+
end
|
619
|
+
end
|
620
|
+
transfers.each { |t| t.wait } # Run file transfers in parallel
|
621
|
+
info $/
|
622
|
+
end
|
623
|
+
|
624
|
+
other.is_a?(StringIO) ? other : nil
|
625
|
+
end
|
626
|
+
|
445
627
|
|
446
628
|
end
|
447
629
|
end
|
data/lib/rye/cmd.rb
CHANGED
@@ -18,6 +18,12 @@ module Rye;
|
|
18
18
|
# rbox.special # => "special on somehost"
|
19
19
|
#
|
20
20
|
module Cmd
|
21
|
+
|
22
|
+
#--
|
23
|
+
# TODO: Clean this trite mess up!
|
24
|
+
#++
|
25
|
+
|
26
|
+
def cd(*args); cmd('cd', args); end
|
21
27
|
def wc(*args); cmd('wc', args); end
|
22
28
|
def cp(*args); cmd("cp", args); end
|
23
29
|
def mv(*args); cmd("mv", args); end
|
@@ -28,8 +34,8 @@ module Rye;
|
|
28
34
|
def df(*args); cmd('df', args); end
|
29
35
|
def du(*args); cmd('du', args); end
|
30
36
|
|
31
|
-
def env
|
32
|
-
def pwd(*args); cmd "pwd"; end
|
37
|
+
def env; cmd "env"; end
|
38
|
+
def pwd(*args); cmd "pwd", args; end
|
33
39
|
def svn(*args); cmd('svn', args); end
|
34
40
|
def cvs(*args); cmd('cvs', args); end
|
35
41
|
def git(*args); cmd('git', args); end
|
@@ -49,40 +55,100 @@ module Rye;
|
|
49
55
|
def mkfs(*args); cmd('mkfs', args); end
|
50
56
|
|
51
57
|
def mount(*args); cmd("mount", args); end
|
52
|
-
def sleep(
|
58
|
+
def sleep(*args); cmd("sleep", args); end
|
53
59
|
def mkdir(*args); cmd('mkdir', args); end
|
54
60
|
def touch(*args); cmd('touch', args); end
|
55
61
|
def uname(*args); cmd('uname', args); end
|
56
62
|
def chmod(*args); cmd('chmod', args); end
|
63
|
+
def chown(*args); cmd('chown', args); end
|
57
64
|
|
58
65
|
def umount(*args); cmd("umount", args); end
|
59
66
|
def uptime(*args); cmd("uptime", args); end
|
60
67
|
def python(*args); cmd('python', args); end
|
68
|
+
def useradd(*args); cmd('useradd', args); end
|
69
|
+
def getconf(*args); cmd('getconf', args); end
|
61
70
|
def history(*args); cmd('history', args); end
|
62
71
|
def printenv(*args); cmd('printenv', args); end
|
63
72
|
def hostname(*args); cmd('hostname', args); end
|
64
73
|
|
65
|
-
|
66
|
-
#
|
67
|
-
#
|
74
|
+
# Transfer files to a machine via Net::SCP.
|
75
|
+
# * +files+ is an Array of files to upload. The last element is the
|
76
|
+
# directory to upload to. If uploading a single file, the last element
|
77
|
+
# can be a file path. The list of files can also include StringIO objects.
|
78
|
+
# The target directory will be created if it does not exist, but only
|
79
|
+
# when multiple files are being transferred.
|
80
|
+
# This method will fail early if there are obvious problems with the input
|
81
|
+
# parameters. An exception is raised and no files are transferred.
|
82
|
+
# Always return nil.
|
68
83
|
#
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
84
|
+
# NOTE: Changes to current working directory with +cd+ or +[]+ are ignored.
|
85
|
+
def upload(*files); net_scp_transfer!(:upload, *files); end
|
86
|
+
|
87
|
+
# Transfer files from a machine via Net::SCP.
|
88
|
+
# * +files+ is an Array of files to download. The last element must be the
|
89
|
+
# local directory to download to. If downloading a single file the last
|
90
|
+
# element can be a file path. The target can also be a StringIO object.
|
91
|
+
# The target directory will be created if it does not exist, but only
|
92
|
+
# when multiple files are being transferred.
|
93
|
+
# This method will fail early if there are obvious problems with the input
|
94
|
+
# parameters. An exception is raised and no files are transferred.
|
95
|
+
# Return nil or a StringIO object, if specified as the target.
|
96
|
+
#
|
97
|
+
# NOTE: Changes to current working directory with +cd+ or +[]+ are ignored.
|
98
|
+
def download(*files); net_scp_transfer!(:download, *files); end
|
99
|
+
|
100
|
+
# Does a remote path exist?
|
101
|
+
def file_exists?(path)
|
102
|
+
begin
|
103
|
+
ret = self.ls(path)
|
104
|
+
rescue Rye::CommandError => ex
|
105
|
+
ret = ex.rap
|
106
|
+
end
|
107
|
+
# "ls" returns a 0 exit code regardless of success in Linux
|
108
|
+
# But on OSX exit code is 1. This is why we look at STDERR.
|
109
|
+
ret.stderr.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the hash containing the parsed output of "env" on the
|
113
|
+
# remote machine. If the initialize option +:getenv+ was set to
|
114
|
+
# false, this will return an empty hash.
|
115
|
+
# This is a lazy loaded method so it fetches the remote envvars
|
116
|
+
# the first time this method is called.
|
117
|
+
#
|
118
|
+
# puts rbox.getenv['HOME'] # => "/home/gloria" (remote)
|
119
|
+
#
|
120
|
+
def getenv
|
121
|
+
if @getenv && @getenv.empty? && self.can?(:env)
|
122
|
+
env = self.env rescue []
|
123
|
+
env.each do |nv|
|
124
|
+
# Parse "GLORIA_HOME=/gloria/lives/here" into a name/value
|
125
|
+
# pair. The regexp ensures we split only at the 1st = sign
|
126
|
+
n, v = nv.scan(/\A([\w_-]+?)=(.+)\z/).flatten
|
127
|
+
@getenv[n] = v
|
128
|
+
end
|
129
|
+
end
|
130
|
+
@getenv
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns an Array of system commands available over SSH
|
134
|
+
def can
|
135
|
+
Rye::Cmd.instance_methods
|
136
|
+
end
|
137
|
+
alias :commands :can
|
138
|
+
alias :cmds :can
|
80
139
|
|
81
|
-
def
|
82
|
-
|
140
|
+
def can?(meth)
|
141
|
+
self.can.member?(RUBY_VERSION =~ /1.9/ ? meth.to_sym : meth.to_s)
|
83
142
|
end
|
143
|
+
alias :command? :can?
|
144
|
+
alias :cmd? :can?
|
145
|
+
|
146
|
+
|
84
147
|
|
85
|
-
|
148
|
+
#--
|
149
|
+
# * Consider a lock-down mode using method_added
|
150
|
+
# * Consider Rye.sysinfo.os == :unix
|
151
|
+
#++
|
86
152
|
end
|
87
153
|
|
88
154
|
end
|
data/lib/rye/rap.rb
CHANGED
@@ -20,7 +20,9 @@ module Rye;
|
|
20
20
|
|
21
21
|
# An array containing any STDERR output
|
22
22
|
attr_reader :stderr
|
23
|
-
|
23
|
+
attr_reader :exit_code
|
24
|
+
# Only populated when calling via Rye::Shell
|
25
|
+
attr_reader :pid
|
24
26
|
attr_accessor :exit_signal
|
25
27
|
|
26
28
|
# The command that was executed.
|
@@ -30,6 +32,8 @@ module Rye;
|
|
30
32
|
# * +args+ anything that can sent to Array#new
|
31
33
|
def initialize(obj, *args)
|
32
34
|
@obj = obj
|
35
|
+
@exit_code = 0
|
36
|
+
@stderr = []
|
33
37
|
super *args
|
34
38
|
end
|
35
39
|
|
@@ -65,12 +69,29 @@ module Rye;
|
|
65
69
|
self.flatten!
|
66
70
|
end
|
67
71
|
|
72
|
+
# Parse the exit code.
|
73
|
+
# * +code+ an exit code string or integer or Process::Status object
|
74
|
+
# For example, when running a command via Rye.shell, this method
|
75
|
+
# is send $? which is Process::Status object. Via Rye::Box.run_command
|
76
|
+
# it's just an exit code returned by Net::SSH.
|
77
|
+
# Returns the exit code as an Integer.
|
78
|
+
def add_exit_code(code)
|
79
|
+
code = -1 unless code
|
80
|
+
if code.is_a?(Process::Status)
|
81
|
+
@exit_code, @pid = code.exitstatus.to_i, code.pid
|
82
|
+
else
|
83
|
+
@exit_code = code.to_i
|
84
|
+
end
|
85
|
+
@exit_code
|
86
|
+
end
|
87
|
+
def code; @exit_code; end
|
88
|
+
|
68
89
|
# Returns the first element if there it's the only
|
69
90
|
# one, otherwise the value of Array#to_s
|
70
91
|
def to_s
|
71
92
|
return self.first if self.size == 1
|
72
93
|
return "" if self.size == 0
|
73
|
-
|
94
|
+
self
|
74
95
|
end
|
75
96
|
|
76
97
|
# NOTE: This is broken!
|
data/lib/rye/set.rb
CHANGED
@@ -68,11 +68,11 @@ module Rye
|
|
68
68
|
|
69
69
|
# Add an environment variable. +n+ and +v+ are the name and value.
|
70
70
|
# Returns the instance of Rye::Set
|
71
|
-
def
|
72
|
-
run_command(:
|
71
|
+
def setenv(n, v)
|
72
|
+
run_command(:setenv, n, v)
|
73
73
|
self
|
74
74
|
end
|
75
|
-
alias :
|
75
|
+
alias :setenvironment_variable :setenv
|
76
76
|
|
77
77
|
# See Rye.keys
|
78
78
|
def keys
|
data/lib/sys.rb
CHANGED
@@ -274,6 +274,19 @@ class SystemInfo #:nodoc:all
|
|
274
274
|
end
|
275
275
|
end
|
276
276
|
|
277
|
+
def tmpdir
|
278
|
+
if @os == :unix
|
279
|
+
(ENV['TMPDIR'] || '/tmp')
|
280
|
+
elsif @os == :win32
|
281
|
+
(ENV['TMPDIR'] || 'C:\\temp')
|
282
|
+
elsif @os == :java
|
283
|
+
default = @impl == :windows ? 'C:\\temp' : '/tmp'
|
284
|
+
(ENV['TMPDIR'] || default)
|
285
|
+
else
|
286
|
+
raise "paths not implemented for: #{@os}"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
277
290
|
# Print friendly system information.
|
278
291
|
def to_s
|
279
292
|
sprintf("Hostname: %s#{$/}IP Address: %s#{$/}System: %s#{$/}Uptime: %.2f (hours)#{$/}Ruby: #{ruby.join('.')}",
|
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.6.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"
|
@@ -15,6 +15,11 @@
|
|
15
15
|
s.add_dependency 'highline'
|
16
16
|
s.add_dependency 'drydock'
|
17
17
|
|
18
|
+
# = EXECUTABLES =
|
19
|
+
# The list of executables in your project (if any). Don't include the path,
|
20
|
+
# just the base filename.
|
21
|
+
s.executables = %w[rye]
|
22
|
+
|
18
23
|
# = MANIFEST =
|
19
24
|
# The complete list of files to be included in the release. When GitHub packages your gem,
|
20
25
|
# it doesn't allow you to run any command that accesses the filesystem. You will get an
|
@@ -44,15 +49,12 @@
|
|
44
49
|
tst/10-key2
|
45
50
|
tst/10-key2.pub
|
46
51
|
tst/10_keys_test.rb
|
47
|
-
tst/
|
52
|
+
tst/50_rset_test.rb
|
53
|
+
tst/60-file.mp3
|
54
|
+
tst/60_rbox_transfer_test.rb
|
55
|
+
tst/70_rbox_env_test.rb
|
48
56
|
)
|
49
57
|
|
50
|
-
# = EXECUTABLES =
|
51
|
-
# The list of executables in your project (if any). Don't include the path,
|
52
|
-
# just the base filename.
|
53
|
-
s.executables = %w[rye]
|
54
|
-
|
55
|
-
|
56
58
|
s.extra_rdoc_files = %w[README.rdoc LICENSE.txt]
|
57
59
|
s.has_rdoc = true
|
58
60
|
s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.rdoc"]
|
data/tst/60-file.mp3
ADDED
Binary file
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Usage: test/60_rbox_upload_test.rb
|
5
|
+
#
|
6
|
+
|
7
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
8
|
+
|
9
|
+
require "rubygems"
|
10
|
+
require "stringio"
|
11
|
+
require "yaml"
|
12
|
+
require "rye"
|
13
|
+
|
14
|
+
tmpdir = Rye.sysinfo.tmpdir
|
15
|
+
|
16
|
+
rbox = Rye::Box.new("localhost", :info => true)
|
17
|
+
def rbox.rm(*args); cmd('rm', args); end
|
18
|
+
rbox.rm(:r, :f, "#{tmpdir}/rye-upload") # Silently delete test dir
|
19
|
+
|
20
|
+
# /tmp/rye-upload will be created if it doesn't exist
|
21
|
+
rbox.upload("README.rdoc", "LICENSE.txt", "#{tmpdir}/rye-upload")
|
22
|
+
|
23
|
+
# A single file can be renamed
|
24
|
+
rbox.upload("README.rdoc", "#{tmpdir}/rye-upload/README-renamed")
|
25
|
+
|
26
|
+
# StringIO objects can be sent as files
|
27
|
+
applejack = StringIO.new
|
28
|
+
applejack.puts "Delano: What's happening Applejack?"
|
29
|
+
applejack.puts "Applejack: Just trying to get by."
|
30
|
+
rbox.upload(applejack, "#{tmpdir}/rye-upload/applejack")
|
31
|
+
|
32
|
+
rbox.upload("tst/60-file.mp3", "#{tmpdir}/rye-upload") # demonstrates
|
33
|
+
rbox.upload("tst/60-file.mp3", "#{tmpdir}/rye-upload") # progress
|
34
|
+
rbox.upload("tst/60-file.mp3", "#{tmpdir}/rye-upload") # bar
|
35
|
+
|
36
|
+
puts "Content of /tmp/rye-upload"
|
37
|
+
puts rbox.ls(:l, "#{tmpdir}/rye-upload")
|
38
|
+
|
39
|
+
rbox.download("#{tmpdir}/rye-upload/README.rdoc",
|
40
|
+
"#{tmpdir}/rye-upload/README-renamed", "#{tmpdir}/rye-download")
|
41
|
+
|
42
|
+
# You can't download a StringIO object. This raises an exception:
|
43
|
+
#rbox.download(applejack, "#{tmpdir}/rye-download/applejack")
|
44
|
+
# But you can download _to_ a StringIO object
|
45
|
+
applejack = StringIO.new
|
46
|
+
rbox.download("#{tmpdir}/rye-upload/applejack", applejack)
|
47
|
+
|
48
|
+
puts $/, "Content of /tmp/rye-download"
|
49
|
+
puts rbox.ls(:l, "#{tmpdir}/rye-download")
|
50
|
+
|
51
|
+
puts $/, "Content of applejack StringIO object"
|
52
|
+
applejack.rewind
|
53
|
+
puts applejack.read
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Usage: test/70_rbox_env_test.rb
|
5
|
+
#
|
6
|
+
|
7
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
8
|
+
|
9
|
+
require "rubygems"
|
10
|
+
require "stringio"
|
11
|
+
require "yaml"
|
12
|
+
require "rye"
|
13
|
+
|
14
|
+
tmpdir = Rye.sysinfo.tmpdir
|
15
|
+
|
16
|
+
rbox = Rye::Box.new("localhost", :info => true)
|
17
|
+
|
18
|
+
puts rbox.getenv['HOME'] # slight delay while vars are fetched
|
19
|
+
puts rbox.getenv['HOME'] # returned from memory
|
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.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
@@ -84,7 +84,10 @@ files:
|
|
84
84
|
- tst/10-key2
|
85
85
|
- tst/10-key2.pub
|
86
86
|
- tst/10_keys_test.rb
|
87
|
-
- tst/
|
87
|
+
- tst/50_rset_test.rb
|
88
|
+
- tst/60-file.mp3
|
89
|
+
- tst/60_rbox_transfer_test.rb
|
90
|
+
- tst/70_rbox_env_test.rb
|
88
91
|
has_rdoc: true
|
89
92
|
homepage: http://solutious.com/
|
90
93
|
post_install_message:
|