rye 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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/