delano-rye 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rye/set.rb ADDED
@@ -0,0 +1,147 @@
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
+ self
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
+ # Ruby 1.8 populates Module.instance_methods with Strings. 1.9 uses Symbols.
95
+ meth = (Rye.sysinfo.ruby[1] == 8) ? meth.to_s : meth.to_sym
96
+ raise Rye::NoBoxes if @boxes.empty?
97
+ raise Rye::CommandNotFound, meth.to_s unless Rye::Cmd.instance_methods.member?(meth)
98
+ run_command(meth, *args)
99
+ end
100
+
101
+ private
102
+
103
+ # Determines whether to call the serial or parallel method, then calls it.
104
+ def run_command(meth, *args)
105
+ runner = @parallel ? :run_command_parallel : :run_command_serial
106
+ self.send(runner, meth, *args)
107
+ end
108
+
109
+
110
+ # Run the command on all boxes in parallel
111
+ def run_command_parallel(meth, *args)
112
+ debug "P: #{meth} on #{@boxes.size} boxes (#{@boxes.collect {|b| b.host }.join(', ')})"
113
+ threads = []
114
+
115
+ raps = Rye::Rap.new(self)
116
+ (@boxes || []).each do |box|
117
+ threads << Thread.new do
118
+ Thread.current[:rap] = box.send(meth, *args) # Store the result in the thread
119
+ end
120
+ end
121
+
122
+ threads.each do |t|
123
+ sleep 0.01 # Give the thread some breathing room
124
+ t.join # Wait for the thread to finish
125
+ raps << t[:rap] # Grab the result
126
+ end
127
+
128
+ raps
129
+ end
130
+
131
+
132
+ # Run the command on all boxes in serial
133
+ def run_command_serial(meth, *args)
134
+ debug "S: #{meth} on #{@boxes.size} boxes (#{@boxes.collect {|b| b.host }.join(', ')})"
135
+ raps = Rye::Rap.new(self)
136
+ (@boxes || []).each do |box|
137
+ raps << box.send(meth, *args)
138
+ end
139
+ raps
140
+ end
141
+
142
+ def debug(msg); @debug.puts msg if @debug; end
143
+ def error(msg); @error.puts msg if @error; end
144
+
145
+ end
146
+
147
+ end
data/lib/sys.rb ADDED
@@ -0,0 +1,274 @@
1
+ require 'socket'
2
+
3
+ # SystemInfo
4
+ #
5
+ # A container for the platform specific system information.
6
+ # Portions of this code were originally from Amazon's EC2 AMI tools,
7
+ # specifically lib/platform.rb.
8
+ class SystemInfo #:nodoc:all
9
+ VERSION = 2
10
+ IMPLEMENTATIONS = [
11
+
12
+ # These are for JRuby, System.getproperty('os.name').
13
+ # For a list of all values, see: http://lopica.sourceforge.net/os.html
14
+ [/mac\s*os\s*x/i, :unix, :osx ],
15
+ [/sunos/i, :unix, :solaris ],
16
+ [/windows\s*ce/i, :win32, :windows ],
17
+ [/windows/i, :win32, :windows ],
18
+ [/osx/i, :unix, :osx ],
19
+
20
+ # TODO: implement other windows matches: # /djgpp|(cyg|ms|bcc)win|mingw/ (from mongrel)
21
+
22
+ # These are for RUBY_PLATFORM and JRuby
23
+ [/java/i, :java, :java ],
24
+ [/darwin/i, :unix, :osx ],
25
+ [/linux/i, :unix, :linux ],
26
+ [/freebsd/i, :unix, :freebsd ],
27
+ [/netbsd/i, :unix, :netbsd ],
28
+ [/solaris/i, :unix, :solaris ],
29
+ [/irix/i, :unix, :irix ],
30
+ [/cygwin/i, :unix, :cygwin ],
31
+ [/mswin/i, :win32, :windows ],
32
+ [/mingw/i, :win32, :mingw ],
33
+ [/bccwin/i, :win32, :bccwin ],
34
+ [/wince/i, :win32, :wince ],
35
+ [/vms/i, :vms, :vms ],
36
+ [/os2/i, :os2, :os2 ],
37
+ [nil, :unknown, :unknown ],
38
+
39
+ ]
40
+
41
+ ARCHITECTURES = [
42
+ [/(i\d86)/i, :i386 ],
43
+ [/x86_64/i, :x86_64 ],
44
+ [/x86/i, :i386 ], # JRuby
45
+ [/ia64/i, :ia64 ],
46
+ [/alpha/i, :alpha ],
47
+ [/sparc/i, :sparc ],
48
+ [/mips/i, :mips ],
49
+ [/powerpc/i, :powerpc ],
50
+ [/universal/i,:universal ],
51
+ [nil, :unknown ],
52
+ ]
53
+
54
+
55
+
56
+ attr_reader :os
57
+ attr_reader :implementation
58
+ attr_reader :architecture
59
+ attr_reader :hostname
60
+ attr_reader :ipaddress
61
+ attr_reader :uptime
62
+
63
+
64
+ alias :impl :implementation
65
+ alias :arch :architecture
66
+
67
+
68
+ def initialize
69
+ @os, @implementation, @architecture = guess
70
+ @hostname, @ipaddress, @uptime = get_info
71
+ end
72
+
73
+ # guess
74
+ #
75
+ # This is called at require-time in stella.rb. It guesses
76
+ # the current operating system, implementation, architecture.
77
+ # Returns [os, impl, arch]
78
+ def guess
79
+ os = :unknown
80
+ impl = :unknown
81
+ arch = :unknown
82
+ IMPLEMENTATIONS.each do |r, o, i|
83
+ if r and RUBY_PLATFORM =~ r
84
+ os, impl = [o, i]
85
+ break
86
+ end
87
+ end
88
+ ARCHITECTURES.each do |r, a|
89
+ if r and RUBY_PLATFORM =~ r
90
+ arch = a
91
+ break
92
+ end
93
+ end
94
+
95
+ #
96
+ if os == :win32
97
+ #require 'Win32API'
98
+
99
+ # If we're running in java, we'll need to look elsewhere
100
+ # for the implementation and architecture.
101
+ # We'll replace IMPL and ARCH with what we find.
102
+ elsif os == :java
103
+ require 'java'
104
+ include_class java.lang.System
105
+
106
+ osname = System.getProperty("os.name")
107
+ IMPLEMENTATIONS.each do |r, o, i|
108
+ if r and osname =~ r
109
+ impl = i
110
+ break
111
+ end
112
+ end
113
+
114
+ osarch = System.getProperty("os.arch")
115
+ ARCHITECTURES.each do |r, a|
116
+ if r and osarch =~ r
117
+ arch = a
118
+ break
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ [os, impl, arch]
125
+ end
126
+
127
+ # get_info
128
+ #
129
+ # Returns [hostname, ipaddr, uptime] for the local machine
130
+ def get_info
131
+ hostname = :unknown
132
+ ipaddr = :unknown
133
+ uptime = :unknown
134
+
135
+ begin
136
+ hostname = local_hostname
137
+ ipaddr = local_ip_address
138
+ uptime = local_uptime
139
+ rescue => ex
140
+ # Be silent!
141
+ end
142
+
143
+ [hostname, ipaddr, uptime]
144
+ end
145
+
146
+ # local_hostname
147
+ #
148
+ # Return the hostname for the local machine
149
+ def local_hostname
150
+ Socket.gethostname
151
+ end
152
+
153
+ # local_uptime
154
+ #
155
+ # Returns the local uptime in hours. Use Win32API in Windows,
156
+ # 'sysctl -b kern.boottime' os osx, and 'who -b' on unix.
157
+ # Based on Ruby Quiz solutions by: Matthias Reitinger
158
+ # On Windows, see also: net statistics server
159
+ def local_uptime
160
+
161
+ # Each method must return uptime in seconds
162
+ methods = {
163
+
164
+ :win32_windows => lambda {
165
+ # Win32API is required in self.guess
166
+ getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L')
167
+ ((getTickCount.call()).to_f / 1000).to_f
168
+ },
169
+
170
+ # Ya, this is kinda wack. Ruby -> Java -> Kernel32. See:
171
+ # http://www.oreillynet.com/ruby/blog/2008/01/jruby_meets_the_windows_api_1.html
172
+ # http://msdn.microsoft.com/en-us/library/ms724408(VS.85).aspx
173
+ # Ruby 1.9.1: Win32API is now deprecated in favor of using the DL library.
174
+ :java_windows => lambda {
175
+ kernel32 = com.sun.jna.NativeLibrary.getInstance('kernel32')
176
+ buf = java.nio.ByteBuffer.allocate(256)
177
+ (kernel32.getFunction('GetTickCount').invokeInt([256, buf].to_java).to_f / 1000).to_f
178
+ },
179
+
180
+ :unix_osx => lambda {
181
+ # This is faster than who and could work on BSD also.
182
+ (Time.now.to_f - Time.at(`sysctl -b kern.boottime 2>/dev/null`.unpack('L').first).to_f).to_f
183
+ },
184
+ # This should work for most unix flavours.
185
+ :unix => lambda {
186
+ # who is sloooooow. Use File.read('/proc/uptime')
187
+ (Time.now.to_f - Time.parse(`who -b 2>/dev/null`).to_f)
188
+ }
189
+ }
190
+
191
+ hours = 0
192
+
193
+ begin
194
+ key = platform
195
+ method = (methods.has_key? key) ? methods[key] : methods[:unix]
196
+ hours = (method.call) / 3600 # seconds to hours
197
+ rescue => ex
198
+ end
199
+ hours
200
+ end
201
+
202
+
203
+ #
204
+ # Return the local IP address which receives external traffic
205
+ # from: http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
206
+ # NOTE: This <em>does not</em> open a connection to the IP address.
207
+ def local_ip_address
208
+ # turn off reverse DNS resolution temporarily
209
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
210
+ UDPSocket.open {|s| s.connect('75.101.137.7', 1); s.addr.last } # Solutious IP
211
+ ensure
212
+ Socket.do_not_reverse_lookup = orig
213
+ end
214
+
215
+ #
216
+ # Returns the local IP address based on the hostname.
217
+ # According to coderrr (see comments on blog link above), this implementation
218
+ # doesn't guarantee that it will return the address for the interface external
219
+ # traffic goes through. It's also possible the hostname isn't resolvable to the
220
+ # local IP.
221
+ def local_ip_address_alt
222
+ ipaddr = :unknown
223
+ begin
224
+ saddr = Socket.getaddrinfo( Socket.gethostname, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
225
+ ipaddr = saddr.select{|type| type[0] == 'AF_INET' }[0][3]
226
+ rescue => ex
227
+ end
228
+ ipaddr
229
+ end
230
+
231
+ # returns a symbol in the form: os_implementation. This is used throughout Stella
232
+ # for platform specific support.
233
+ def platform
234
+ "#{@os}_#{@implementation}".to_sym
235
+ end
236
+
237
+ # Returns Ruby version as an array
238
+ def ruby
239
+ RUBY_VERSION.split('.').map { |v| v.to_i }
240
+ end
241
+
242
+ # Returns the environment PATH as an Array
243
+ def paths
244
+ if @os == :unix
245
+ (ENV['PATH'] || '').split(':')
246
+ elsif
247
+ (ENV['PATH'] || '').split(';') # Note tested!
248
+ else
249
+ raise "paths not implemented for: #{@os}"
250
+ end
251
+ end
252
+
253
+ def user
254
+ ENV['USER']
255
+ end
256
+
257
+ def home
258
+ if @os == :unix
259
+ File.expand_path(ENV['HOME'])
260
+ elsif @os == :win32
261
+ File.expand_path(ENV['USERPROFILE'])
262
+ else
263
+ raise "paths not implemented for: #{@os}"
264
+ end
265
+ end
266
+
267
+ # Print friendly system information.
268
+ def to_s
269
+ sprintf("Hostname: %s#{$/}IP Address: %s#{$/}System: %s#{$/}Uptime: %.2f (hours)#{$/}Ruby: #{ruby.join('.')}",
270
+ @hostname, @ipaddress, "#{@os}-#{@implementation}-#{@architecture}", @uptime)
271
+ end
272
+
273
+
274
+ end
data/rye.gemspec ADDED
@@ -0,0 +1,56 @@
1
+ @spec = Gem::Specification.new do |s|
2
+ s.name = "rye"
3
+ s.rubyforge_project = "rye"
4
+ s.version = "0.3.2"
5
+ s.summary = "Rye: Run system commands via SSH locally and remotely in a Ruby way."
6
+ s.description = s.summary
7
+ s.author = "Delano Mandelbaum"
8
+ s.email = "delano@solutious.com"
9
+ s.homepage = "http://solutious.com/"
10
+
11
+ # = DEPENDENCIES =
12
+ # Add all gem dependencies
13
+ s.add_dependency 'net-ssh'
14
+ s.add_dependency 'highline'
15
+
16
+ # = MANIFEST =
17
+ # The complete list of files to be included in the release. When GitHub packages your gem,
18
+ # it doesn't allow you to run any command that accesses the filesystem. You will get an
19
+ # error. You can ask your VCS for the list of versioned files:
20
+ # git ls-files
21
+ # svn list -R
22
+ s.files = %w(
23
+ CHANGES.txt
24
+ LICENSE.txt
25
+ README.rdoc
26
+ Rakefile
27
+ bin/try
28
+ lib/esc.rb
29
+ lib/rye.rb
30
+ lib/rye/box.rb
31
+ lib/rye/cmd.rb
32
+ lib/rye/rap.rb
33
+ lib/rye/set.rb
34
+ lib/sys.rb
35
+ rye.gemspec
36
+ test/10_rye_test.rb
37
+ )
38
+
39
+ # = EXECUTABLES =
40
+ # The list of executables in your project (if any). Don't include the path,
41
+ # just the base filename.
42
+ #s.executables = %w[]
43
+
44
+
45
+ s.extra_rdoc_files = %w[README.rdoc LICENSE.txt]
46
+ s.has_rdoc = true
47
+ s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.rdoc"]
48
+ s.require_paths = %w[lib]
49
+ s.rubygems_version = '1.3.0'
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 2
54
+ end
55
+
56
+ end