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 +7 -0
- data/README.rdoc +31 -3
- data/lib/rye.rb +4 -3
- data/lib/rye/box.rb +64 -4
- data/lib/rye/cmd.rb +2 -1
- data/lib/rye/hop.rb +410 -0
- data/lib/rye/key.rb +2 -1
- data/lib/rye/rap.rb +2 -2
- data/lib/rye/set.rb +3 -1
- data/rye.gemspec +3 -3
- metadata +5 -4
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
|
-
|
55
|
+
Rye::Cmd.add_command :anything, '/usr/bin/uptime'
|
56
|
+
rbox = Rye::Box.new
|
56
57
|
rbox.anything
|
57
|
-
|
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.
|
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]
|
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
|
-
|
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
|
-
|
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
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
data/lib/rye/rap.rb
CHANGED
data/lib/rye/set.rb
CHANGED
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.
|
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:
|
4
|
+
hash: 61
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
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:
|
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/
|