rye 0.9.2 → 0.9.3

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
@@ -1,5 +1,12 @@
1
1
  RYE, CHANGES
2
2
 
3
+ #### 0.9.3 (2011-01-29) #############################
4
+
5
+ * ADDED: Rye::Hop for gateway support.
6
+ (https://github.com/delano/rye/issues/closed#issue/10)
7
+ * CHANGE: Don't overwrite :paranoid option
8
+
9
+
3
10
  #### 0.9.2 (2010-10-25) #############################
4
11
 
5
12
  * FIXED: add_keys stores key paths for Rye::Box and Rye::Set
data/README.rdoc CHANGED
@@ -52,9 +52,10 @@ You can specify environment variables.
52
52
 
53
53
  You can add and remove commands to the whitelist.
54
54
 
55
- rbox.add_command :anything, '/path/2/anything'
55
+ Rye::Cmd.add_command :anything, '/usr/bin/uptime'
56
+ rbox = Rye::Box.new
56
57
  rbox.anything
57
- rbox.remove_command :anything
58
+ Rye::Cmd.remove_command :anything
58
59
  rbox.anything # => Rye::CommandNotFound exception
59
60
 
60
61
 
@@ -139,6 +140,32 @@ The return value is a Rye::Rap object (just like with Rye::Box) so you have acce
139
140
  ret.class # => Rye::Rap
140
141
 
141
142
 
143
+ == Example 8a -- Hopping Firewalls
144
+
145
+ When working with machines that are behind another host (assuming that you have ssh access to the firewall host):
146
+
147
+ rhop = Rye::Hop.new('firewall.lan')
148
+ rbox = Rye::Box.new('filibuster', :via => rhop)
149
+ rbox.uptime # => 20:53 up 1 day, 1:52, 4 users
150
+
151
+ Or
152
+
153
+ rbox = Rye::Box.new('filibuster', :via => 'firewall.lan')
154
+
155
+ The information for the Rye::Box is then relative from the position of the firewall.
156
+ So, the hostname 'filibuster' is used from 'firewall.lan'
157
+
158
+ == Example 8b -- Hopping Firewalls, in groups
159
+
160
+ rset = Rye::Set.new "guarded_few", :parallel => true
161
+ rhop = Rye::Hop.new "firewall.lan"
162
+ rbox1 = Rye::Box.new "192.168.1.10", :via => rhop
163
+ rbox2 = Rye::Box.new "192.168.1.15", :via => rhop
164
+ rset.add_boxes rbox1, rbox2
165
+ rset.uptime
166
+ # => [[17:17:44 up 548 days, 13:37, 20 users, load average: 0.12, 0.07, 0.06], [01:17:49 up 6 days, 1:39, 9 users, load average: 0.13, 0.09, 0.09]]
167
+
168
+
142
169
  == About Safe-Mode
143
170
 
144
171
  In safe-mode:
@@ -195,6 +222,7 @@ If you find one let me know!
195
222
  * Rush[http://github.com/adamwiggins/rush] and Capistrano[http://github.com/jamis/capistrano/blob/master/lib/capistrano/shell.rb] for the inspiration.
196
223
  * Mike Cline for giving the okay to use the Rye name.
197
224
  * Justin Case (http://github.com/justincase/) for fixes
225
+ * Vincent Batts (http://hashbangbash.com/) for Rye::Hop
198
226
 
199
227
 
200
228
  == More Info
@@ -218,4 +246,4 @@ If you find one let me know!
218
246
 
219
247
  == License
220
248
 
221
- See: LICENSE.txt
249
+ See: LICENSE.txt
data/lib/rye.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # vim: set sw=2 ts=2 :
1
2
 
2
3
  require 'logger'
3
4
  require 'thread'
@@ -42,7 +43,7 @@ require 'esc'
42
43
  module Rye
43
44
  extend self
44
45
 
45
- VERSION = "0.9.2".freeze unless defined?(VERSION)
46
+ VERSION = "0.9.3".freeze unless defined?(VERSION)
46
47
 
47
48
  @@sysinfo = nil
48
49
  @@agent_env = Hash.new # holds ssh-agent env vars
@@ -86,7 +87,7 @@ module Rye
86
87
  # NOTE: does not reload rye.rb.
87
88
  def reload
88
89
  pat = File.join(File.dirname(__FILE__), 'rye')
89
- %w{key rap cmd box set}.each {|lib| load File.join(pat, "#{lib}.rb") }
90
+ %w{key rap cmd box set hop}.each {|lib| load File.join(pat, "#{lib}.rb") }
90
91
  end
91
92
 
92
93
  def mutex
@@ -294,4 +295,4 @@ end
294
295
 
295
296
 
296
297
 
297
-
298
+
data/lib/rye/box.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # vim: set sw=2 ts=2 :
2
+
1
3
  require 'annoy'
2
4
  require 'readline'
3
5
 
@@ -50,10 +52,12 @@ module Rye
50
52
  # Returns the current value of the stash +@rye_stash+
51
53
  def stash; @rye_stash; end
52
54
  def quiet; @rye_quiet; end
55
+ def via; @rye_via; end
53
56
  def nickname; @rye_nickname || host; end
54
57
 
55
58
  def host=(val); @rye_host = val; end
56
59
  def opts=(val); @rye_opts = val; end
60
+ def via=(val); @rye_via = val; end
57
61
 
58
62
  # Store a value to the stash +@rye_stash+
59
63
  def stash=(val); @rye_stash = val; end
@@ -72,6 +76,7 @@ module Rye
72
76
  # The most recent valud for umask (or 0022)
73
77
  def current_umask; @rye_current_umask; end
74
78
 
79
+ def via?; !@rye_via.nil?; end
75
80
  def info?; !@rye_info.nil?; end
76
81
  def debug?; !@rye_debug.nil?; end
77
82
  def error?; !@rye_error.nil?; end
@@ -93,6 +98,7 @@ module Rye
93
98
  # * :safe => should Rye be safe? Default: true
94
99
  # * :port => remote server ssh port. Default: SSH config file or 22
95
100
  # * :keys => one or more private key file paths (passwordless login)
101
+ # * :via => the Rye::Hop to access this host through
96
102
  # * :info => an IO object to print Rye::Box command info to. Default: nil
97
103
  # * :debug => an IO object to print Rye::Box debugging info to. Default: nil
98
104
  # * :error => an IO object to print Rye::Box errors to. Default: STDERR
@@ -120,6 +126,7 @@ module Rye
120
126
  :safe => true,
121
127
  :port => ssh_opts[:port],
122
128
  :keys => Rye.keys,
129
+ :via => nil,
123
130
  :info => nil,
124
131
  :debug => nil,
125
132
  :error => STDERR,
@@ -131,6 +138,9 @@ module Rye
131
138
  # Close the SSH session before Ruby exits. This will do nothing
132
139
  # if disconnect has already been called explicitly.
133
140
  at_exit { self.disconnect }
141
+
142
+ # Properly handle whether the opt :via is a +Rye::Hop+ or a +String+
143
+ via_hop(@rye_opts.delete(:via))
134
144
 
135
145
  # @rye_opts gets sent to Net::SSH so we need to remove the keys
136
146
  # that are not meant for it.
@@ -149,7 +159,7 @@ module Rye
149
159
  end
150
160
 
151
161
  @rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
152
- @rye_opts[:paranoid] = true unless @rye_safe == false # See Net::SSH.start
162
+ @rye_opts[:paranoid] ||= true unless @rye_safe == false # See Net::SSH.start
153
163
  @rye_opts[:keys] = [@rye_opts[:keys]].flatten.compact
154
164
 
155
165
  # Just in case someone sends a true value rather than IO object
@@ -178,6 +188,39 @@ module Rye
178
188
  return Net::SSH::Config.for(host)
179
189
  end
180
190
 
191
+ # * +hops+ Rye::Hop objects will be added directly
192
+ # to the set. Hostnames will be used to create new instances of Rye::Hop
193
+ # h1 = Rye::Hop.new "host1"
194
+ # h1.via_hop "host2", :user => "service_user"
195
+ #
196
+ # OR
197
+ #
198
+ # h1 = Rye::Hop.new "host1"
199
+ # h2 = Rye::Hop.new "host2"
200
+ # h1.via_hop h2
201
+ #
202
+ def via_hop(*args)
203
+ args = args.flatten.compact
204
+ if args.first.nil?
205
+ return @rye_via
206
+ elsif args.first.is_a?(Rye::Hop)
207
+ @rye_via = args.first
208
+ elsif args.first.is_a?(String)
209
+ hop = args.shift
210
+ if args.first.is_a?(Hash)
211
+ @rye_via = Rye::Hop.new(hop, args.first.merge(
212
+ :debug => @rye_debug,
213
+ :info => @rye_info,
214
+ :error => @rye_error)
215
+ )
216
+ else
217
+ @rye_via = Rye::Hop.new(hop)
218
+ end
219
+ end
220
+ disconnect
221
+ self
222
+ end
223
+
181
224
  # Change the current working directory (sort of).
182
225
  #
183
226
  # I haven't been able to wrangle Net::SSH to do my bidding.
@@ -602,12 +645,24 @@ module Rye
602
645
  raise Rye::NoHost unless @rye_host
603
646
  return if @rye_ssh && !reconnect
604
647
  disconnect if @rye_ssh
605
- debug "Opening connection to #{@rye_host} as #{@rye_user}"
648
+ if @rye_via
649
+ debug "Opening connection to #{@rye_host} as #{@rye_user}, via #{@rye_via.host}"
650
+ else
651
+ debug "Opening connection to #{@rye_host} as #{@rye_user}"
652
+ end
606
653
  highline = HighLine.new # Used for password prompt
607
654
  retried = 0
608
655
  @rye_opts[:keys].compact! # A quick fix in Windows. TODO: Why is there a nil?
609
656
  begin
610
- @rye_ssh = Net::SSH.start(@rye_host, @rye_user, @rye_opts || {})
657
+ if @rye_via
658
+ # tell the +Rye::Hop+ what and where to setup,
659
+ # it returns the local port used
660
+ @rye_localport = @rye_via.fetch_port(@rye_host, @rye_opts[:port].nil? ? 22 : @rye_opts[:port] )
661
+ debug "fetched localport #{@rye_localport}"
662
+ @rye_ssh = Net::SSH.start("localhost", @rye_user, @rye_opts.merge(:port => @rye_localport) || {})
663
+ else
664
+ @rye_ssh = Net::SSH.start(@rye_host, @rye_user, @rye_opts || {})
665
+ end
611
666
  rescue Net::SSH::HostKeyMismatch => ex
612
667
  STDERR.puts ex.message
613
668
  print "\a" if @rye_info # Ring the bell
@@ -653,8 +708,13 @@ module Rye
653
708
  end
654
709
  debug "Closing connection to #{@rye_ssh.host}"
655
710
  @rye_ssh.close
711
+ if @rye_via
712
+ debug "disconnecting Hop #{@rye_via.host}"
713
+ @rye_via.disconnect
714
+ end
656
715
  rescue SystemCallError, Timeout::Error => ex
657
- error "Disconnect timeout"
716
+ error "Rye::Box: Disconnect timeout (#{ex.message})"
717
+ debug ex.backtrace
658
718
  rescue Interrupt
659
719
  debug "Exiting..."
660
720
  end
data/lib/rye/cmd.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # vim: set sw=2 ts=2 :
1
2
 
2
3
  module Rye;
3
4
 
@@ -370,4 +371,4 @@ module Rye;
370
371
  #++
371
372
  end
372
373
 
373
- end
374
+ end
data/lib/rye/hop.rb ADDED
@@ -0,0 +1,410 @@
1
+ # vim : set sw=2 ts=2 :
2
+
3
+ require 'socket'
4
+
5
+ module Rye
6
+ DEBUG = false unless defined?(Rye::DEBUG)
7
+
8
+ # = Rye::Hop
9
+ #
10
+ # The Rye::Hop class represents a machine.
11
+ # This class allows boxes to by accessed via it.
12
+ #
13
+ # rhop = Rye::Hop.new('firewall.lan')
14
+ # rbox = Rye::Box.new('filibuster', :via => rhop)
15
+ # rbox.uptime # => 20:53 up 1 day, 1:52, 4 users
16
+ #
17
+ # Or
18
+ #
19
+ # rbox = Rye::Box.new('filibuster', :via => 'firewall.lan')
20
+ #
21
+ #--
22
+ # * When anything confusing happens, enable debug in initialize
23
+ # by passing :debug => STDERR. This will output Rye debug info
24
+ # as well as Net::SSH info. This is VERY helpful for figuring
25
+ # out why some command is hanging or otherwise acting weird.
26
+ # * If a remote command is hanging, it's probably because a
27
+ # Net::SSH channel is waiting on_extended_data (a prompt).
28
+ #++
29
+ class Hop
30
+
31
+ # The maximum port number that the gateway will attempt to use to forward
32
+ # connections from.
33
+ MAX_PORT = 65535
34
+
35
+ # The minimum port number that the gateway will attempt to use to forward
36
+ # connections from.
37
+ MIN_PORT = 1024
38
+
39
+ def host; @rye_host; end
40
+ def opts; @rye_opts; end
41
+ def user; @rye_user; end
42
+ def root?; user.to_s == "root" end
43
+
44
+ def nickname; @rye_nickname || host; end
45
+ def via; @rye_via; end
46
+
47
+ def nickname=(val); @rye_nickname = val; end
48
+ def host=(val); @rye_host = val; end
49
+ def opts=(val); @rye_opts = val; end
50
+
51
+
52
+ def via?; !@rye_via.nil?; end
53
+ def info?; !@rye_info.nil?; end
54
+ def debug?; !@rye_debug.nil?; end
55
+ def error?; !@rye_error.nil?; end
56
+
57
+
58
+ # A Hash. The keys are exception classes, the values are Procs to execute
59
+ def exception_hook=(val); @rye_exception_hook = val; end
60
+
61
+ # * +host+ The hostname to connect to. Default: localhost.
62
+ # * +user+ The username to connect as. Default: SSH config file or current shell user.
63
+ # * +opts+ a hash of optional arguments.
64
+ #
65
+ # The +opts+ hash excepts the following keys:
66
+ #
67
+ # * :port => remote server ssh port. Default: SSH config file or 22
68
+ # * :keys => one or more private key file paths (passwordless login)
69
+ # * :via => the Rye::Hop to access this host through
70
+ # * :info => an IO object to print Rye::Box command info to. Default: nil
71
+ # * :debug => an IO object to print Rye::Box debugging info to. Default: nil
72
+ # * :error => an IO object to print Rye::Box errors to. Default: STDERR
73
+ # * :getenv => pre-fetch +host+ environment variables? (default: true)
74
+ # * :password => the user's password (ignored if there's a valid private key)
75
+ # * :templates => the template engine to use for uploaded files. One of: :erb (default)
76
+ # * :sudo => Run all commands via sudo (default: false)
77
+ #
78
+ # NOTE: +opts+ can also contain any parameter supported by
79
+ # Net::SSH.start that is not already mentioned above.
80
+ #
81
+ def initialize(host, opts={})
82
+ ssh_opts = ssh_config_options(host)
83
+ @rye_exception_hook = {}
84
+ @rye_host = host
85
+
86
+ if opts[:user]
87
+ @rye_user = opts[:user]
88
+ else
89
+ @rye_user = ssh_opts[:user] || Rye.sysinfo.user
90
+ end
91
+
92
+ # These opts are use by Rye::Box and also passed to
93
+ # Net::SSH::Gateway (and Net::SSH)
94
+ @rye_opts = {
95
+ :port => ssh_opts[:port],
96
+ :keys => Rye.keys,
97
+ :via => nil,
98
+ :info => nil,
99
+ :debug => nil,
100
+ :error => STDERR,
101
+ :getenv => true,
102
+ :templates => :erb,
103
+ :quiet => false
104
+ }.merge(opts)
105
+
106
+ @next_port = MAX_PORT
107
+
108
+ # Close the SSH session before Ruby exits. This will do nothing
109
+ # if disconnect has already been called explicitly.
110
+ at_exit { self.disconnect }
111
+
112
+ # Properly handle whether the opt :via is a +Rye::Hop+ or a +String+
113
+ # and does nothing if nil
114
+ via_hop(@rye_opts.delete(:via))
115
+
116
+ # @rye_opts gets sent to Net::SSH so we need to remove the keys
117
+ # that are not meant for it.
118
+ @rye_safe, @rye_debug = @rye_opts.delete(:safe), @rye_opts.delete(:debug)
119
+ @rye_info, @rye_error = @rye_opts.delete(:info), @rye_opts.delete(:error)
120
+ @rye_getenv = {} if @rye_opts.delete(:getenv) # Enable getenv with a hash
121
+ @rye_ostype, @rye_impltype = @rye_opts.delete(:ostype), @rye_opts.delete(:impltype)
122
+ @rye_quiet, @rye_sudo = @rye_opts.delete(:quiet), @rye_opts.delete(:sudo)
123
+ @rye_templates = @rye_opts.delete(:templates)
124
+
125
+ # Store the state of the terminal
126
+ @rye_stty_save = `stty -g`.chomp rescue nil
127
+
128
+ unless @rye_templates.nil?
129
+ require @rye_templates.to_s # should be :erb
130
+ end
131
+
132
+ @rye_opts[:logger] = Logger.new(@rye_debug) if @rye_debug # Enable Net::SSH debugging
133
+ @rye_opts[:keys] = [@rye_opts[:keys]].flatten.compact
134
+
135
+ # Just in case someone sends a true value rather than IO object
136
+ @rye_debug = STDERR if @rye_debug == true || DEBUG
137
+ @rye_error = STDERR if @rye_error == true
138
+ @rye_info = STDOUT if @rye_info == true
139
+
140
+ # Add the given private keys to the keychain that will be used for @rye_host
141
+ add_keys(@rye_opts[:keys])
142
+
143
+ # From: capistrano/lib/capistrano/cli.rb
144
+ STDOUT.sync = true # so that Net::SSH prompts show up
145
+
146
+ debug "ssh-agent info: #{Rye.sshagent_info.inspect}"
147
+ debug @rye_opts.inspect
148
+ end
149
+
150
+ # * +hops+ Rye::Hop objects will be added directly
151
+ # to the set. Hostnames will be used to create new instances of Rye::Hop
152
+ # h1 = Rye::Hop.new "host1"
153
+ # h1.via_hop "host2", :user => "service_user"
154
+ #
155
+ # OR
156
+ #
157
+ # h1 = Rye::Hop.new "host1"
158
+ # h2 = Rye::Hop.new "host2"
159
+ # h1.via_hop h2
160
+ #
161
+ def via_hop(*hops)
162
+ hops = hops.flatten.compact
163
+ if hops.first.nil?
164
+ return @rye_via
165
+ elsif hops.first.is_a?(Rye::Hop)
166
+ @rye_via = hops.first
167
+ elsif hops.first.is_a?(String)
168
+ hop = hops.shift
169
+ if hops.first.is_a?(Hash)
170
+ @rye_via = Rye::Hop.new(hop, hops.first)
171
+ else
172
+ @rye_via = Rye::Hop.new(hop)
173
+ end
174
+ end
175
+ disconnect
176
+ self
177
+ end
178
+
179
+ # instance method, that will setup a forward, and
180
+ # return the port used
181
+ def fetch_port(host, port = 22, localport = nil)
182
+ connect unless @rye_ssh
183
+ if localport.nil?
184
+ port_used = next_port
185
+ else
186
+ port_used = localport
187
+ end
188
+ # i would like to check if the port and host
189
+ # are already an active_locals forward, but that
190
+ # info does not get returned, and trusting the localport
191
+ # is not enough information, so lets just set up a new one
192
+ @rye_ssh.forward.local(port_used, host, port)
193
+ return port_used
194
+ end
195
+
196
+ # Parse SSH config files for use with Net::SSH
197
+ def ssh_config_options(host)
198
+ return Net::SSH::Config.for(host)
199
+ end
200
+
201
+ # Add one or more private keys to the list of key paths.
202
+ # * +keys+ is a list of file paths to private keys
203
+ # Returns the instance of Box
204
+ def add_keys(*keys)
205
+ @rye_opts[:keys] ||= []
206
+ @rye_opts[:keys] += keys.flatten.compact
207
+ @rye_opts[:keys].uniq!
208
+ self # MUST RETURN self
209
+ end
210
+ alias :add_key :add_keys
211
+
212
+ # Remove one or more private keys fromt he list of key paths.
213
+ # * +keys+ is a list of file paths to private keys
214
+ # Returns the instance of Box
215
+ def remove_keys(*keys)
216
+ @rye_opts[:keys] ||= []
217
+ @rye_opts[:keys] -= keys.flatten.compact
218
+ @rye_opts[:keys].uniq!
219
+ self # MUST RETURN self
220
+ end
221
+ alias :remove_key :remove_keys
222
+
223
+ # Reconnect as another user. This is different from su=
224
+ # which executes subsequent commands via +su -c COMMAND USER+.
225
+ # * +newuser+ The username to reconnect as
226
+ #
227
+ # NOTE: if there is an open connection, it's disconnected
228
+ # but not reconnected because it's possible it wasn't
229
+ # connected yet in the first place (if you create the
230
+ # instance with default settings for example)
231
+ def switch_user(newuser)
232
+ return if newuser.to_s == self.user.to_s
233
+ @rye_opts ||= {}
234
+ @rye_user = newuser
235
+ disconnect
236
+ end
237
+
238
+ # Open an SSH session with +@rye_host+. This called automatically
239
+ # when you the first comamnd is run if it's not already connected.
240
+ # Raises a Rye::NoHost exception if +@rye_host+ is not specified.
241
+ # Will attempt a password login up to 3 times if the initial
242
+ # authentication fails.
243
+ # * +reconnect+ Disconnect first if already connected. The default
244
+ # is true. When set to false, connect will do nothing if already
245
+ # connected.
246
+ def connect(reconnect=true)
247
+ raise Rye::NoHost unless @rye_host
248
+ return if @rye_ssh && !reconnect
249
+ disconnect if @rye_ssh
250
+ if @rye_via
251
+ debug "Opening connection to #{@rye_host} as #{@rye_user}, via #{@rye_via.host}"
252
+ else
253
+ debug "Opening connection to #{@rye_host} as #{@rye_user}"
254
+ end
255
+ highline = HighLine.new # Used for password prompt
256
+ retried = 0
257
+ @rye_opts[:keys].compact! # A quick fix in Windows. TODO: Why is there a nil?
258
+ begin
259
+ if @rye_via
260
+ # tell the +Rye::Hop+ what and where to setup,
261
+ # it returns the local port used
262
+ @rye_localport = @rye_via.fetch_port(@rye_host, @rye_opts[:port].nil? ? 22 : @rye_opts[:port] )
263
+ @rye_ssh = Net::SSH.start("localhost", @rye_user, @rye_opts.merge(:port => @rye_localport) || {})
264
+ else
265
+ @rye_ssh = Net::SSH.start(@rye_host, @rye_user, @rye_opts || {})
266
+ end
267
+ debug "starting the port forward thread"
268
+ port_loop
269
+ rescue Net::SSH::HostKeyMismatch => ex
270
+ STDERR.puts ex.message
271
+ print "\a" if @rye_info # Ring the bell
272
+ if highline.ask("Continue? ").strip.match(/\Ay|yes|sure|ya\z/i)
273
+ @rye_opts[:paranoid] = false
274
+ retry
275
+ else
276
+ raise Net::SSH::HostKeyMismatch
277
+ end
278
+ rescue Net::SSH::AuthenticationFailed => ex
279
+ print "\a" if retried == 0 && @rye_info # Ring the bell once
280
+ retried += 1
281
+ if STDIN.tty? && retried <= 3
282
+ STDERR.puts "Passwordless login failed for #{@rye_user}"
283
+ @rye_opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }.strip
284
+ @rye_opts[:auth_methods] ||= []
285
+ @rye_opts[:auth_methods].push *['keyboard-interactive', 'password']
286
+ retry
287
+ else
288
+ raise Net::SSH::AuthenticationFailed
289
+ end
290
+ end
291
+
292
+ # We add :auth_methods (a Net::SSH joint) to force asking for a
293
+ # password if the initial (key-based) authentication fails. We
294
+ # need to delete the key from @rye_opts otherwise it lingers until
295
+ # the next connection (if we switch_user is called for example).
296
+ @rye_opts.delete :auth_methods if @rye_opts.has_key?(:auth_methods)
297
+
298
+ self
299
+ end
300
+
301
+ # Cancel the port forward on all active local forwards
302
+ def remove_hops!
303
+ return unless @rye_ssh && @rye_ssh.forward.active_locals.count > 0
304
+ @rye_ssh.forward.active_locals.each {|fport, fhost|
305
+ @rye_ssh.forward.cancel_local(fport, fhost)
306
+ }
307
+ if !@rye_ssh.channels.empty?
308
+ @rye_ssh.channels.each {|channel|
309
+ channel[-1].close
310
+ }
311
+ end
312
+ return @rye_ssh.forward.active_locals.count
313
+ end
314
+
315
+ # Close the SSH session with +@rye_host+. This is called
316
+ # automatically at exit if the connection is open.
317
+ def disconnect
318
+ return unless @rye_ssh && !@rye_ssh.closed?
319
+ begin
320
+ debug "removing active forwards"
321
+ remove_hops!
322
+ debug "killing port_loop @rye_port_thread"
323
+ @rye_port_thread.kill
324
+ if @rye_ssh.busy?;
325
+ info "Is something still running? (ctrl-C to exit)"
326
+ Timeout::timeout(10) do
327
+ @rye_ssh.loop(0.3) { @rye_ssh.busy?; }
328
+ end
329
+ end
330
+ debug "Closing connection to #{@rye_ssh.host}"
331
+ @rye_ssh.close
332
+ if @rye_via
333
+ debug "disconnecting Hop #{@rye_via.host}"
334
+ @rye_via.disconnect
335
+ end
336
+ rescue SystemCallError, Timeout::Error => ex
337
+ error "Rye::Hop: Disconnect timeout (#{ex.message})"
338
+ debug ex.backtrace
339
+ rescue Interrupt
340
+ debug "Exiting..."
341
+ end
342
+ end
343
+
344
+ # See Rye.keys
345
+ def keys; Rye.keys; end
346
+
347
+ # Returns +user@rye_host+
348
+ def to_s; '%s@rye_%s' % [user, @rye_host]; end
349
+
350
+ def inspect
351
+ %q{#<%s:%s name=%s cwd=%s umask=%s env=%s via=%s opts=%s keys=%s>} %
352
+ [self.class.to_s, self.host, self.nickname,
353
+ @rye_current_working_directory, @rye_current_umask,
354
+ (@rye_current_environment_variables || '').inspect,
355
+ (@rye_via || '').inspect,
356
+ self.opts.inspect, self.keys.inspect]
357
+ end
358
+
359
+ # Compares itself with the +other+ box. If the hostnames
360
+ # are the same, this will return true. Otherwise false.
361
+ def ==(other)
362
+ @rye_host == other.host
363
+ end
364
+
365
+ # Returns the host SSH keys for this box
366
+ def host_key
367
+ raise "No host" unless @rye_host
368
+ Rye.remote_host_keys(@rye_host)
369
+ end
370
+
371
+ private
372
+ # Kicks off the thread that maintains the forwards
373
+ # if additional +Rye::Box+es add this +Rye::Hop+ as their via,
374
+ # it'll keep on trucking
375
+ def port_loop
376
+ connect unless @rye_ssh
377
+ @active = true
378
+ @rye_port_thread = Thread.new do
379
+ while @active
380
+ @rye_ssh.process(0.1)
381
+ end
382
+ end
383
+ end
384
+
385
+ # Grabs the next available port number and returns it.
386
+ def next_port
387
+ port = @next_port
388
+ @next_port -= 1
389
+ @next_port = MAX_PORT if @next_port < MIN_PORT
390
+ # check if the port is in use, if so get the next_port
391
+ begin
392
+ TCPSocket.new '127.0.0.1', port
393
+ rescue Errno::EADDRINUSE
394
+ next_port()
395
+ rescue Errno::ECONNREFUSED
396
+ port
397
+ else
398
+ next_port()
399
+ end
400
+ end
401
+
402
+ def debug(msg="unknown debug msg"); @rye_debug.puts msg if @rye_debug; end
403
+ def error(msg="unknown error msg"); @rye_error.puts msg if @rye_error; end
404
+ def pinfo(msg="unknown info msg"); @rye_info.print msg if @rye_info; end
405
+ def info(msg="unknown info msg"); @rye_info.puts msg if @rye_info; end
406
+
407
+ end
408
+
409
+ end
410
+
data/lib/rye/key.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # vim: set sw=2 ts=2 :
1
2
 
2
3
  module Rye
3
4
  class Key
@@ -131,4 +132,4 @@ module Rye
131
132
  end
132
133
 
133
134
  end
134
- end
135
+ end
data/lib/rye/rap.rb CHANGED
@@ -1,4 +1,4 @@
1
-
1
+ # vim: set sw=2 ts=2 :
2
2
 
3
3
  module Rye;
4
4
 
@@ -143,4 +143,4 @@ module Rye;
143
143
 
144
144
  end
145
145
 
146
- end
146
+ end
data/lib/rye/set.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # vim: set sw=2 ts=2 :
2
+
1
3
  module Rye
2
4
 
3
5
  # = Rye::Set
@@ -192,4 +194,4 @@ module Rye
192
194
 
193
195
  end
194
196
 
195
- end
197
+ end
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.9.2"
4
+ s.version = "0.9.3"
5
5
  s.summary = "Rye: Safely run SSH commands on a bunch of machines at the same time (from Ruby)."
6
6
  s.description = s.summary
7
7
  s.author = "Delano Mandelbaum"
@@ -16,7 +16,6 @@
16
16
  s.add_dependency 'highline', '>= 1.5.1'
17
17
  s.add_dependency 'net-ssh', '>= 2.0.13'
18
18
  s.add_dependency 'net-scp', '>= 1.0.2'
19
- #s.add_dependency 'net-ssh-gateway'
20
19
  #s.add_dependency 'net-ssh-multi'
21
20
 
22
21
  # = EXECUTABLES =
@@ -44,6 +43,7 @@
44
43
  lib/rye/key.rb
45
44
  lib/rye/rap.rb
46
45
  lib/rye/set.rb
46
+ lib/rye/hop.rb
47
47
  rye.gemspec
48
48
  )
49
49
 
@@ -58,4 +58,4 @@
58
58
  s.specification_version = 2
59
59
  end
60
60
 
61
- end
61
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rye
3
3
  version: !ruby/object:Gem::Version
4
- hash: 63
4
+ hash: 61
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 2
10
- version: 0.9.2
9
+ - 3
10
+ version: 0.9.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Delano Mandelbaum
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-25 00:00:00 -04:00
18
+ date: 2011-01-29 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -119,6 +119,7 @@ files:
119
119
  - lib/rye/key.rb
120
120
  - lib/rye/rap.rb
121
121
  - lib/rye/set.rb
122
+ - lib/rye/hop.rb
122
123
  - rye.gemspec
123
124
  has_rdoc: true
124
125
  homepage: http://github.com/delano/rye/