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 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.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
data/README.rdoc CHANGED
@@ -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
 
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
- # * :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
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(*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
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
- 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!
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 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('.')}",
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.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')
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.5.4
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/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
  post_install_message: