rye 0.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 +26 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +176 -0
- data/Rakefile +83 -0
- data/bin/try +148 -0
- data/lib/esc.rb +301 -0
- data/lib/rye.rb +155 -0
- data/lib/rye/box.rb +312 -0
- data/lib/rye/cmd.rb +47 -0
- data/lib/rye/rap.rb +83 -0
- data/lib/rye/set.rb +145 -0
- data/lib/sys.rb +274 -0
- data/rye.gemspec +56 -0
- data/test/10_rye_test.rb +63 -0
- metadata +90 -0
data/lib/rye/box.rb
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rye
|
4
|
+
|
5
|
+
|
6
|
+
# = Rye::Box
|
7
|
+
#
|
8
|
+
# The Rye::Box class represents a machine. All system
|
9
|
+
# commands are made through this class.
|
10
|
+
#
|
11
|
+
# rbox = Rye::Box.new('filibuster')
|
12
|
+
# rbox.hostname # => filibuster
|
13
|
+
# rbox.uname # => FreeBSD
|
14
|
+
# rbox.uptime # => 20:53 up 1 day, 1:52, 4 users
|
15
|
+
#
|
16
|
+
# You can also run local commands through SSH
|
17
|
+
#
|
18
|
+
# rbox = Rye::Box.new('localhost')
|
19
|
+
# rbox.hostname # => localhost
|
20
|
+
# rbox.uname # => Darwin
|
21
|
+
#
|
22
|
+
class Box
|
23
|
+
include Rye::Cmd
|
24
|
+
|
25
|
+
# An instance of Net::SSH::Connection::Session
|
26
|
+
attr_reader :ssh
|
27
|
+
|
28
|
+
attr_reader :debug
|
29
|
+
attr_reader :error
|
30
|
+
|
31
|
+
attr_accessor :host
|
32
|
+
|
33
|
+
attr_accessor :safe
|
34
|
+
attr_accessor :opts
|
35
|
+
|
36
|
+
|
37
|
+
# * +host+ The hostname to connect to. The default is localhost.
|
38
|
+
# * +opts+ a hash of optional arguments.
|
39
|
+
#
|
40
|
+
# The +opts+ hash excepts the following keys:
|
41
|
+
#
|
42
|
+
# * :user => the username to connect as. Default: the current user.
|
43
|
+
# * :safe => should Rye be safe? Default: true
|
44
|
+
# * :keys => one or more private key file paths (passwordless login)
|
45
|
+
# * :password => the user's password (ignored if there's a valid private key)
|
46
|
+
# * :debug => an IO object to print Rye::Box debugging info to. Default: nil
|
47
|
+
# * :error => an IO object to print Rye::Box errors to. Default: STDERR
|
48
|
+
#
|
49
|
+
# NOTE: +opts+ can also contain any parameter supported by
|
50
|
+
# Net::SSH.start that is not already mentioned above.
|
51
|
+
#
|
52
|
+
def initialize(host='localhost', opts={})
|
53
|
+
|
54
|
+
# These opts are use by Rye::Box and also passed to Net::SSH
|
55
|
+
@opts = {
|
56
|
+
:user => Rye.sysinfo.user,
|
57
|
+
:safe => true,
|
58
|
+
:port => 22,
|
59
|
+
:keys => [],
|
60
|
+
:debug => nil,
|
61
|
+
:error => STDERR,
|
62
|
+
}.merge(opts)
|
63
|
+
|
64
|
+
# See Net::SSH.start
|
65
|
+
@opts[:paranoid] = true unless @opts[:safe] == false
|
66
|
+
|
67
|
+
# Close the SSH session before Ruby exits. This will do nothing
|
68
|
+
# if disconnect has already been called explicitly.
|
69
|
+
at_exit {
|
70
|
+
self.disconnect
|
71
|
+
}
|
72
|
+
|
73
|
+
@host = host
|
74
|
+
|
75
|
+
@safe = @opts.delete(:safe)
|
76
|
+
@debug = @opts.delete(:debug)
|
77
|
+
@error = @opts.delete(:error)
|
78
|
+
|
79
|
+
add_keys(@opts[:keys])
|
80
|
+
|
81
|
+
# We don't want Net::SSH to handle the keypairs. This may change
|
82
|
+
# but for we're letting ssh-agent do it.
|
83
|
+
@opts.delete(:keys)
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# Returns an Array of system commands available over SSH
|
89
|
+
def can
|
90
|
+
Rye::Cmd.instance_methods
|
91
|
+
end
|
92
|
+
alias :commands :can
|
93
|
+
alias :cmds :can
|
94
|
+
|
95
|
+
|
96
|
+
# Change the current working directory (sort of).
|
97
|
+
#
|
98
|
+
# I haven't been able to wrangle Net::SSH to do my bidding.
|
99
|
+
# "My bidding" in this case, is maintaining an open channel between commands.
|
100
|
+
# I'm using Net::SSH::Connection::Session#exec! for all commands
|
101
|
+
# which is like a funky helper method that opens a new channel
|
102
|
+
# each time it's called. This seems to be okay for one-off
|
103
|
+
# commands but changing the directory only works for the channel
|
104
|
+
# it's executed in. The next time exec! is called, there's a
|
105
|
+
# new channel which is back in the default (home) directory.
|
106
|
+
#
|
107
|
+
# Long story short, the work around is to maintain the current
|
108
|
+
# directory locally and send it with each command.
|
109
|
+
#
|
110
|
+
# rbox.pwd # => /home/rye ($ pwd )
|
111
|
+
# rbox['/usr/bin'].pwd # => /usr/bin ($ cd /usr/bin && pwd)
|
112
|
+
# rbox.pwd # => /usr/bin ($ cd /usr/bin && pwd)
|
113
|
+
#
|
114
|
+
def [](key=nil)
|
115
|
+
@current_working_directory = key
|
116
|
+
self
|
117
|
+
end
|
118
|
+
alias :cd :'[]'
|
119
|
+
|
120
|
+
|
121
|
+
# Open an SSH session with +@host+. This called automatically
|
122
|
+
# when you the first comamnd is run if it's not already connected.
|
123
|
+
# Raises a Rye::NoHost exception if +@host+ is not specified.
|
124
|
+
def connect
|
125
|
+
raise Rye::NoHost unless @host
|
126
|
+
disconnect if @ssh
|
127
|
+
debug "Opening connection to #{@host}"
|
128
|
+
@ssh = Net::SSH.start(@host, @opts[:user], @opts || {})
|
129
|
+
@ssh.is_a?(Net::SSH::Connection::Session) && !@ssh.closed?
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
# Close the SSH session with +@host+. This is called
|
134
|
+
# automatically at exit if the connection is open.
|
135
|
+
def disconnect
|
136
|
+
return unless @ssh && !@ssh.closed?
|
137
|
+
@ssh.loop(0.1) { @ssh.busy? }
|
138
|
+
debug "Closing connection to #{@ssh.host}"
|
139
|
+
@ssh.close
|
140
|
+
end
|
141
|
+
|
142
|
+
# Add one or more private keys to the SSH Agent.
|
143
|
+
# * +additional_keys+ is a list of file paths to private keys
|
144
|
+
# Returns the instance of Box
|
145
|
+
def add_keys(*additional_keys)
|
146
|
+
additional_keys = [additional_keys].flatten.compact || []
|
147
|
+
Rye.add_keys(additional_keys)
|
148
|
+
self
|
149
|
+
end
|
150
|
+
alias :add_key :add_keys
|
151
|
+
|
152
|
+
# Add an environment variable. +n+ and +v+ are the name and value.
|
153
|
+
# Returns the instance of Rye::Box
|
154
|
+
def add_env(n, v)
|
155
|
+
debug "Added env: #{n}=#{v}"
|
156
|
+
(@current_environment_variables ||= {})[n] = v
|
157
|
+
self
|
158
|
+
end
|
159
|
+
alias :add_environment_variable :add_env
|
160
|
+
|
161
|
+
# See Rye.keys
|
162
|
+
def keys
|
163
|
+
Rye.keys
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# Takes a command with arguments and returns it in a
|
168
|
+
# single String with escaped args and some other stuff.
|
169
|
+
#
|
170
|
+
# * +cmd+ The shell command name or absolute path.
|
171
|
+
# * +args+ an Array of command arguments.
|
172
|
+
#
|
173
|
+
# The command is searched for in the local PATH (where
|
174
|
+
# Rye is running). An exception is raised if it's not
|
175
|
+
# found. NOTE: Because this happens locally, you won't
|
176
|
+
# want to use this method if the environment is quite
|
177
|
+
# different from the remote machine it will be executed
|
178
|
+
# on.
|
179
|
+
#
|
180
|
+
# The command arguments are passed through Escape.shell_command
|
181
|
+
# (that means you can't use environment variables or asterisks).
|
182
|
+
#
|
183
|
+
def Box.prepare_command(cmd, *args)
|
184
|
+
args &&= [args].flatten.compact
|
185
|
+
cmd = Rye::Box.which(cmd)
|
186
|
+
raise CommandNotFound.new(cmd || 'nil') unless cmd
|
187
|
+
Rye::Box.escape(@safe, cmd, *args)
|
188
|
+
end
|
189
|
+
|
190
|
+
# An all ruby implementation of unix "which" command.
|
191
|
+
#
|
192
|
+
# * +executable+ the name of the executable
|
193
|
+
#
|
194
|
+
# Returns the absolute path if found in PATH otherwise nil.
|
195
|
+
def Box.which(executable)
|
196
|
+
return unless executable.is_a?(String)
|
197
|
+
#return executable if File.exists?(executable) # SHOULD WORK, MUST TEST
|
198
|
+
shortname = File.basename(executable)
|
199
|
+
dir = Rye.sysinfo.paths.select do |path| # dir contains all of the
|
200
|
+
next unless File.exists? path # occurrences of shortname
|
201
|
+
Dir.new(path).entries.member?(shortname) # found in the paths.
|
202
|
+
end
|
203
|
+
File.join(dir.first, shortname) unless dir.empty? # Return just the first
|
204
|
+
end
|
205
|
+
|
206
|
+
# Execute a local system command (via the shell, not SSH)
|
207
|
+
#
|
208
|
+
# * +cmd+ the executable path (relative or absolute)
|
209
|
+
# * +args+ Array of arguments to be sent to the command. Each element
|
210
|
+
# is one argument:. i.e. <tt>['-l', 'some/path']</tt>
|
211
|
+
#
|
212
|
+
# NOTE: shell is a bit paranoid so it escapes every argument. This means
|
213
|
+
# you can only use literal values. That means no asterisks too.
|
214
|
+
#
|
215
|
+
def Box.shell(cmd, args=[])
|
216
|
+
# TODO: allow stdin to be send to cmd
|
217
|
+
cmd = Box.prepare_command(cmd, args)
|
218
|
+
cmd << " 2>&1" # Redirect STDERR to STDOUT. Works in DOS also.
|
219
|
+
handle = IO.popen(cmd, "r")
|
220
|
+
output = handle.read.chomp
|
221
|
+
handle.close
|
222
|
+
output
|
223
|
+
end
|
224
|
+
|
225
|
+
# Creates a string from +cmd+ and +args+. If +safe+ is true
|
226
|
+
# it will send them through Escape.shell_command otherwise
|
227
|
+
# it will return them joined by a space character.
|
228
|
+
def Box.escape(safe, cmd, *args)
|
229
|
+
args = args.flatten.compact || []
|
230
|
+
safe ? Escape.shell_command(cmd, *args).to_s : [cmd, args].flatten.compact.join(' ')
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
|
236
|
+
def debug(msg); @debug.puts msg if @debug; end
|
237
|
+
def error(msg); @error.puts msg if @error; end
|
238
|
+
|
239
|
+
|
240
|
+
# Add the current environment variables to the beginning of +cmd+
|
241
|
+
def prepend_env(cmd)
|
242
|
+
return cmd unless @current_environment_variables.is_a?(Hash)
|
243
|
+
env = ''
|
244
|
+
@current_environment_variables.each_pair do |n,v|
|
245
|
+
env << "export #{n}=#{Escape.shell_single_word(v)}; "
|
246
|
+
end
|
247
|
+
[env, cmd].join(' ')
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
# Execute a command over SSH
|
252
|
+
#
|
253
|
+
# * +args+ is a command name and list of arguments.
|
254
|
+
# The command name is the literal name of the command
|
255
|
+
# that will be executed in the remote shell. The arguments
|
256
|
+
# will be thoroughly escaped and passed to the command.
|
257
|
+
#
|
258
|
+
# rbox = Rye::Box.new
|
259
|
+
# rbox.ls '-l', 'arg1', 'arg2'
|
260
|
+
#
|
261
|
+
# is equivalent to
|
262
|
+
#
|
263
|
+
# $ ls -l 'arg1' 'arg2'
|
264
|
+
#
|
265
|
+
# This method will try to connect to the host automatically
|
266
|
+
# but if it fails it will raise a Rye::NotConnected exception.
|
267
|
+
#
|
268
|
+
def add_command(*args)
|
269
|
+
connect if !@ssh || @ssh.closed?
|
270
|
+
args = args.first.split(/\s+/) if args.size == 1
|
271
|
+
cmd, args = args.flatten.compact
|
272
|
+
|
273
|
+
raise Rye::NotConnected, @host unless @ssh && !@ssh.closed?
|
274
|
+
|
275
|
+
cmd_clean = Rye::Box.escape(@safe, cmd, args)
|
276
|
+
cmd_clean = prepend_env(cmd_clean)
|
277
|
+
if @current_working_directory
|
278
|
+
cwd = Rye::Box.escape(@safe, 'cd', @current_working_directory)
|
279
|
+
cmd_clean = [cwd, cmd_clean].join('; ')
|
280
|
+
end
|
281
|
+
debug "Executing: %s" % cmd_clean
|
282
|
+
stdout, stderr = net_ssh_exec! cmd_clean
|
283
|
+
rap = Rye::Rap.new(self)
|
284
|
+
rap.add_stdout(stdout || '')
|
285
|
+
rap.add_stderr(stderr || '')
|
286
|
+
rap
|
287
|
+
end
|
288
|
+
alias :cmd :add_command
|
289
|
+
|
290
|
+
# Executes +command+ via SSH
|
291
|
+
# Returns an Array with two elements, [stdout, stderr], representing
|
292
|
+
# the STDOUT and STDERR output by the command. They're Strings.
|
293
|
+
def net_ssh_exec!(command)
|
294
|
+
block ||= Proc.new do |ch, type, data|
|
295
|
+
ch[:stdout] ||= ""
|
296
|
+
ch[:stderr] ||= ""
|
297
|
+
ch[:stdout] << data if type == :stdout
|
298
|
+
ch[:stderr] << data if type == :stderr
|
299
|
+
end
|
300
|
+
|
301
|
+
channel = @ssh.exec(command, &block)
|
302
|
+
channel.wait # block until we get a response
|
303
|
+
|
304
|
+
[channel[:stdout], channel[:stderr]]
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
|
data/lib/rye/cmd.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
module Rye;
|
3
|
+
|
4
|
+
# = Rye::Cmd
|
5
|
+
#
|
6
|
+
# This class contains all of the shell command methods
|
7
|
+
# available to an instance of Rye::Box. For security and
|
8
|
+
# general safety, Rye only permits this whitelist of
|
9
|
+
# commands by default. However, you're free to add methods
|
10
|
+
# with mixins.
|
11
|
+
#
|
12
|
+
# require 'rye'
|
13
|
+
# module Rye::Box::Cmd
|
14
|
+
# def special(*args); cmd("/your/special/command", args); end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# rbox = Rye::Box.new
|
18
|
+
# rbox.special # => "your output"
|
19
|
+
#
|
20
|
+
module Cmd
|
21
|
+
def wc(*args); cmd('wc', args); end
|
22
|
+
def cp(*args); cmd("cp", args); end
|
23
|
+
def mv(*args); cmd("mv", args); end
|
24
|
+
def ls(*args); cmd('ls', args); end
|
25
|
+
def rm(*args); cmd('rm', args); end
|
26
|
+
def ps(*args); cmd('ps', args); end
|
27
|
+
def sh(*args); cmd('sh', args); end
|
28
|
+
def env; cmd "env"; end
|
29
|
+
def pwd; cmd "pwd"; end
|
30
|
+
def cat(*args); cmd('cat', args); end
|
31
|
+
def grep(*args); cmd('grep', args); end
|
32
|
+
def date(*args); cmd('date', args); end
|
33
|
+
def ruby(*args); cmd('ruby', args); end
|
34
|
+
def perl(*args); cmd('perl', args); end
|
35
|
+
def bash(*args); cmd('bash', args); end
|
36
|
+
def echo(*args); cmd('echo', args); end
|
37
|
+
def sleep(seconds=1); cmd("sleep", seconds); end
|
38
|
+
def touch(*args); cmd('touch', args); end
|
39
|
+
def uname(*args); cmd('uname', args); end
|
40
|
+
def mount; cmd("mount"); end
|
41
|
+
def python(*args); cmd('python', args); end
|
42
|
+
def uptime; cmd("uptime"); end
|
43
|
+
def printenv(*args); cmd('printenv', args); end
|
44
|
+
# Consider Rye.sysinfo.os == :unix
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/rye/rap.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rye;
|
4
|
+
|
5
|
+
# Rye::Rap
|
6
|
+
#
|
7
|
+
# This class is a modified Array which is returned by
|
8
|
+
# all command methods. The command output is split
|
9
|
+
# by line into an instance of this class. If there is
|
10
|
+
# only a single element it will act like a String.
|
11
|
+
#
|
12
|
+
# This class also contains a reference to the instance
|
13
|
+
# of Rye::Box or Rye::Set that the command was executed
|
14
|
+
# on.
|
15
|
+
#
|
16
|
+
class Rap < Array
|
17
|
+
# A reference to the Rye object instance the command
|
18
|
+
# was executed by (Rye::Box or Rye::Set)
|
19
|
+
attr_reader :obj
|
20
|
+
|
21
|
+
# An array containing any STDERR output
|
22
|
+
attr_reader :stderr
|
23
|
+
|
24
|
+
# * +obj+ an instance of Rye::Box or Rye::Set
|
25
|
+
# * +args+ anything that can sent to Array#new
|
26
|
+
def initialize(obj, *args)
|
27
|
+
@obj = obj
|
28
|
+
super *args
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :box :obj
|
32
|
+
alias :set :obj
|
33
|
+
|
34
|
+
# Returns a reference to the Rye::Rap object (which
|
35
|
+
# acts like an Array that contains the STDOUT from the
|
36
|
+
# command executed over SSH). This is available to
|
37
|
+
# maintain consistency with the stderr method.
|
38
|
+
def stdout
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add STDERR output from the command executed via SSH.
|
43
|
+
def add_stderr(*args)
|
44
|
+
args = args.flatten.compact
|
45
|
+
args = args.first.split($/) if args.size == 1
|
46
|
+
@stderr ||= []
|
47
|
+
@stderr << args
|
48
|
+
@stderr.flatten!
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add STDOUT output from the command executed via SSH.
|
53
|
+
# This is available to maintain consistency with the
|
54
|
+
# add_stderr method. Otherwise there's no need to use
|
55
|
+
# this method (treat the Rye::Rap object like an Array).
|
56
|
+
def add_stdout(*args)
|
57
|
+
args = args.flatten.compact
|
58
|
+
args = args.first.split($/) if args.size == 1
|
59
|
+
self << args
|
60
|
+
self.flatten!
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the first element if there it's the only
|
64
|
+
# one, otherwise the value of Array#to_s
|
65
|
+
def to_s
|
66
|
+
return self.first if self.size == 1
|
67
|
+
return "" if self.size == 0
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
#---
|
72
|
+
# If Box's shell methods return Rap objects, then
|
73
|
+
# we can do stuff like this
|
74
|
+
# rbox.cp '/etc' | rbox2['/tmp']
|
75
|
+
#def |(other)
|
76
|
+
# puts "BOX1", self.join($/)
|
77
|
+
# puts "BOX2", other.join($/)
|
78
|
+
#end
|
79
|
+
#+++
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
data/lib/rye/set.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
module Rye
|
2
|
+
|
3
|
+
# = Rye::Set
|
4
|
+
#
|
5
|
+
#
|
6
|
+
class Set
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :boxes
|
9
|
+
|
10
|
+
# * +name+ The name of the set of machines
|
11
|
+
# * +opts+ a hash of optional arguments
|
12
|
+
#
|
13
|
+
# The +opts+ hash is used as defaults for all for all Rye::Box objects.
|
14
|
+
# All args supported by Rye::Box are available here with the addition of:
|
15
|
+
#
|
16
|
+
# * :parallel => run the commands in parallel? true or false (default).
|
17
|
+
#
|
18
|
+
def initialize(name='default', opts={})
|
19
|
+
@name = name
|
20
|
+
@boxes = []
|
21
|
+
|
22
|
+
# These opts are use by Rye::Box and also passed to Net::SSH
|
23
|
+
@opts = {
|
24
|
+
:parallel => false,
|
25
|
+
:user => Rye.sysinfo.user,
|
26
|
+
:safe => true,
|
27
|
+
:port => 22,
|
28
|
+
:keys => [],
|
29
|
+
:password => nil,
|
30
|
+
:proxy => nil,
|
31
|
+
:debug => nil,
|
32
|
+
:error => STDERR,
|
33
|
+
}.merge(opts)
|
34
|
+
|
35
|
+
@parallel = @opts.delete(:parallel) # Rye::Box doesn't have :parallel
|
36
|
+
|
37
|
+
@safe = @opts.delete(:safe)
|
38
|
+
@debug = @opts.delete(:debug)
|
39
|
+
@error = @opts.delete(:error)
|
40
|
+
|
41
|
+
add_keys(@opts[:keys])
|
42
|
+
end
|
43
|
+
|
44
|
+
# * +boxes+ one or more boxes. Rye::Box objects will be added directly
|
45
|
+
# to the set. Hostnames will be used to create new instances of Rye::Box
|
46
|
+
# and those will be added to the list.
|
47
|
+
def add_box(*boxes)
|
48
|
+
boxes = boxes.flatten.compact
|
49
|
+
@boxes += boxes.collect do |box|
|
50
|
+
box.is_a?(Rye::Box) ? box.add_keys(@keys) : Rye::Box.new(box, @opts)
|
51
|
+
end
|
52
|
+
@boxes
|
53
|
+
end
|
54
|
+
alias :add_boxes :add_box
|
55
|
+
|
56
|
+
# Add one or more private keys to the SSH Agent.
|
57
|
+
# * +additional_keys+ is a list of file paths to private keys
|
58
|
+
# Returns the instance of Rye::Set
|
59
|
+
def add_key(*additional_keys)
|
60
|
+
additional_keys = [additional_keys].flatten.compact || []
|
61
|
+
Rye.add_keys(additional_keys)
|
62
|
+
self
|
63
|
+
end
|
64
|
+
alias :add_keys :add_key
|
65
|
+
|
66
|
+
# Add an environment variable. +n+ and +v+ are the name and value.
|
67
|
+
# Returns the instance of Rye::Set
|
68
|
+
def add_env(n, v)
|
69
|
+
run_command(:add_env, n, v)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
alias :add_environment_variable :add_env
|
73
|
+
|
74
|
+
# See Rye.keys
|
75
|
+
def keys
|
76
|
+
Rye.keys
|
77
|
+
end
|
78
|
+
|
79
|
+
# See Rye::Box.[]
|
80
|
+
def [](key=nil)
|
81
|
+
run_command(:cd, key)
|
82
|
+
self
|
83
|
+
end
|
84
|
+
alias :cd :'[]'
|
85
|
+
|
86
|
+
# Catches calls to Rye::Box commands. If +meth+ is the name of an
|
87
|
+
# instance method defined in Rye::Cmd then we call it against all
|
88
|
+
# the boxes in +@boxes+. Otherwise this method raises a
|
89
|
+
# Rye::CommandNotFound exception. It will also raise a Rye::NoBoxes
|
90
|
+
# exception if this set has no boxes defined.
|
91
|
+
#
|
92
|
+
# Returns a Rye::Rap object containing the responses from each Rye::Box.
|
93
|
+
def method_missing(meth, *args)
|
94
|
+
raise Rye::NoBoxes if @boxes.empty?
|
95
|
+
raise Rye::CommandNotFound, meth.to_s unless Rye::Cmd.instance_methods.member?(meth.to_s)
|
96
|
+
run_command(meth, *args)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Determines whether to call the serial or parallel method, then calls it.
|
102
|
+
def run_command(meth, *args)
|
103
|
+
runner = @parallel ? :run_command_parallel : :run_command_serial
|
104
|
+
self.send(runner, meth, *args)
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# Run the command on all boxes in parallel
|
109
|
+
def run_command_parallel(meth, *args)
|
110
|
+
debug "P: #{meth} on #{@boxes.size} boxes (#{@boxes.collect {|b| b.host }.join(', ')})"
|
111
|
+
threads = []
|
112
|
+
|
113
|
+
raps = Rye::Rap.new(self)
|
114
|
+
(@boxes || []).each do |box|
|
115
|
+
threads << Thread.new do
|
116
|
+
Thread.current[:rap] = box.send(meth, *args) # Store the result in the thread
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
threads.each do |t|
|
121
|
+
sleep 0.01 # Give the thread some breathing room
|
122
|
+
t.join # Wait for the thread to finish
|
123
|
+
raps << t[:rap] # Grab the result
|
124
|
+
end
|
125
|
+
|
126
|
+
raps
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
# Run the command on all boxes in serial
|
131
|
+
def run_command_serial(meth, *args)
|
132
|
+
debug "S: #{meth} on #{@boxes.size} boxes (#{@boxes.collect {|b| b.host }.join(', ')})"
|
133
|
+
raps = Rye::Rap.new(self)
|
134
|
+
(@boxes || []).each do |box|
|
135
|
+
raps << box.send(meth, *args)
|
136
|
+
end
|
137
|
+
raps
|
138
|
+
end
|
139
|
+
|
140
|
+
def debug(msg); @debug.puts msg if @debug; end
|
141
|
+
def error(msg); @error.puts msg if @error; end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|