delano-rye 0.3.2

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