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 +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/
|