rye 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.authorize_keys was not disabling safe mode properly
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
@@ -1,4 +1,4 @@
1
- = Rye - v0.5
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.add_env(:RYE, "Forty Creek")
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.add_env(:RYE, "Forty Creek")
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
- == EXAMPLE 6 -- rye
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.authorize_keys
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
- $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
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.add_env(:RYE, "Forty Creek")
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.add_env(:RYE, "Forty Creek")
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 # => 1
154
- puts ex.stderr # => ls: rye.test: No such file or directory
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 error so the exit code should be 0.
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.5.freeze
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.exit_code = $?
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
 
@@ -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
- # * :password => the user's password (ignored if there's a valid private key)
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
- @safe = @opts.delete(:safe)
78
- @debug = @opts.delete(:debug)
79
- @error = @opts.delete(:error)
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
- debug @opts.inspect
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
- debug "ssh-agent info: #{Rye.sshagent_info.inspect}"
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
- # alias :cd :'[]' # fix for jruby
127
- def cd(key=nil);
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
- def connect
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
- # Close the SSH session with +@host+. This is called
179
- # automatically at exit if the connection is open.
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 add_env(n, v)
233
- debug "Added env: #{n}=#{v}"
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 :add_environment_variable :add_env
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, (@current_environment_variables || '').inspect,
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 an Array of the private keys files used to generate the public keys.
275
- #
276
- # NOTE: authorize_keys disables safe-mode for this box while it runs
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 authorize_keys
331
+ def authorize_keys_remote(other_user=nil)
332
+ this_user = other_user || @user
281
333
  added_keys = []
282
- @safe= false
283
- Rye.keys.each do |key|
284
- path = key[2]
285
- debug "# Public key for #{path}"
286
- k = Rye::Key.from_file(path).public_key.to_ssh2
287
- self.mkdir(:p, :m, '700', '$HOME/.ssh') # Silently create dir if it doesn't exist
288
- self.echo("'#{k}' >> $HOME/.ssh/authorized_keys")
289
- self.echo("'#{k}' >> $HOME/.ssh/authorized_keys2")
290
- self.chmod('-R', '0600', '$HOME/.ssh/authorized_keys*')
291
- added_keys << path
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
- @safe = true
294
- added_keys
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
- # authorize_keys except run with local shell commands.
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 '-l', 'arg1', 'arg2'
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
- stdout, stderr, ecode, esignal = net_ssh_exec! cmd_clean
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.exit_code = ecode
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
- a = "-#{a}" if a.is_a?(Symbol) && a.to_s.size == 1
402
- a = "--#{a}" if a.is_a?(Symbol)
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] ||= 0
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
@@ -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(*args); cmd "env"; end
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(seconds=1); cmd("sleep", seconds); end
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
- # def copy_to(*boxes)
67
- # p boxes
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
- # @scp = Net::SCP.start(@host, @opts[:user], @opts || {})
70
- # #@ssh.is_a?(Net::SSH::Connection::Session) && !@ssh.closed?
71
- # p @scp
72
- # end
73
-
74
-
75
- #def copy_to(*args)
76
- # args = [args].flatten.compact || []
77
- # other = args.pop
78
- # p other
79
- #end
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 exists?
82
- cmd("uptime");
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
- # Consider Rye.sysinfo.os == :unix
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
@@ -20,7 +20,9 @@ module Rye;
20
20
 
21
21
  # An array containing any STDERR output
22
22
  attr_reader :stderr
23
- attr_accessor :exit_code
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
- super
94
+ self
74
95
  end
75
96
 
76
97
  # NOTE: This is broken!
@@ -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 add_env(n, v)
72
- run_command(:add_env, n, v)
71
+ def setenv(n, v)
72
+ run_command(:setenv, n, v)
73
73
  self
74
74
  end
75
- alias :add_environment_variable :add_env
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('.')}",
@@ -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.5.4"
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/50_rye_test.rb
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"]
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
3
  #
4
- # Usage: test/10_rye_test.rb
4
+ # Usage: test/50_rye_test.rb
5
5
  #
6
6
 
7
7
  $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
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: rye
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.6.0
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-04-22 00:00:00 -04:00
12
+ date: 2009-04-28 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -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/50_rye_test.rb
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
  licenses: []