Skip to content

Commit

Permalink
version 0.1 extracted and working
Browse files Browse the repository at this point in the history
  • Loading branch information
timcharper committed Sep 17, 2010
1 parent 0e101a6 commit fcdd242
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 0 deletions.
20 changes: 20 additions & 0 deletions MIT-LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2009 Tim Harper

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 changes: 9 additions & 0 deletions bin/start_services
@@ -0,0 +1,9 @@
#!/usr/bin/env ruby
require "rubygems"
require "service_manager"
ServiceManager.load_services ARGV[0]
ServiceManager.start
begin
sleep
rescue Interrupt => e
end
59 changes: 59 additions & 0 deletions lib/service_manager.rb
@@ -0,0 +1,59 @@
require 'tcpsocket-wait'
require 'background_process'
require 'net/http'

module ServiceManager
SERVICES_PATH = "./config/services.rb"

extend self

def services
@services ||= []
end

def load_services(path = nil)
path ||= SERVICES_PATH
return if @services_loaded
load path
@services_loaded = true
end

def define_service(name = nil, &block)
name ||= File.basename(caller.first.gsub(/.rb:.+$/, ""))
ServiceManager::Service.new(:name => name).tap do |service|
yield service
services << service
end
end

def services_hash
Hash[ServiceManager.services.map { |s| [s.name.to_sym, s]}]
end

def stop(which = :all)
puts "Stopping the services..."
services.map {|s| Thread.new { s.stop } }.map(&:join)
end

def start(which = :all)
load_services
raise RuntimeError, "No services defined" if services.empty?
threads = services.map do |s|
Thread.new do
begin
s.start
rescue ServiceManager::Service::ServerDidntStart
puts "Quitting due to failure."
exit(1)
rescue Exception => e
puts e
puts e.backtrace
exit(1)
end
end
end
threads.map(&:join)
end
end

require "service_manager/service"
118 changes: 118 additions & 0 deletions lib/service_manager/service.rb
@@ -0,0 +1,118 @@
class ServiceManager::Service
NORMAL_COLOR = 37

attr_accessor :name, :host, :port, :cwd, :reload_uri, :start_cmd, :process, :loaded_cue, :timeout, :color

class ServerDidntStart < Exception; end

def initialize(options = {})
options.each { |k,v| send("#{k}=", v) }
self.host ||= "localhost"
self.color ||= NORMAL_COLOR
self.timeout ||= 30
raise ArgumentError, "You need to provide a name for this app service" unless name
end

def url
"http://#{host}:#{port}"
end

def server_info_hash
{:name => name, :host => host, :port => port}
end

def watch_for_cue
process.detect(:both, timeout) do |output|
STDOUT << colorize(output)
output =~ loaded_cue
end
end

def start_output_stream_thread
Thread.new { process.detect { |output| STDOUT << colorize(output); nil} }
end

def start_cmd
@start_cmd.is_a?(Proc) ? instance_eval(&@start_cmd) : @start_cmd
end

def without_bundler_env(&block)
vars = %w{BUNDLE_PATH BUNDLE_GEMFILE BUNDLE_BIN_PATH}
old_values = vars.map {|v| ENV.delete(v)}
yield
vars.zip(old_values).each { |var, value| ENV[var] = value }
end

def start
if running?
puts "Server for #{colorized_service_name} detected as running."
reload || puts("Reloading not supported. Any changes made to code for #{colorized_service_name} will not take effect!")
return false
end

puts "Starting #{colorized_service_name} in #{cwd} with '#{start_cmd}'"
Dir.chdir(cwd) do
without_bundler_env do
# system("bash -c set")
self.process = PTYBackgroundProcess.run(start_cmd)
end
end
at_exit { stop }
wait
puts "Server #{colorized_service_name} is up."
end

def stop
return unless process
puts "Shutting down #{colorized_service_name}"
process.kill
process.wait(3)
if process.running?
process.kill("KILL") # ok... no more Mr. Nice Guy.
process.wait
end
puts "Server #{colorized_service_name} (#{process.pid}) is shut down"
self.process = nil
true
end

def reload
return false unless reload_uri
puts "Reloading #{colorized_service_name} app by hitting http://#{host}:#{port}#{reload_uri} ..."
res = Net::HTTP.start(host, port) {|http| http.get(reload_uri) }
raise("Reloading app #{colorized_service_name} did not return a 200! It returned a #{res.code}. Output:\n#{colorize(res.body)}") unless res.code.to_i == 200
true
end

def running?
TCPSocket.listening_service?(:port => port, :host => host)
end

protected
def colorize(output)
"\e[0;#{color}m#{output}\e[0;#{NORMAL_COLOR}m"
end

def colorized_service_name
if process
colorize("#{name} (#{process.pid})")
else
colorize("#{name}")
end
end

def wait
if loaded_cue
raise(ServerDidntStart) unless watch_for_cue
start_output_stream_thread
else
start_output_stream_thread
begin
TCPSocket.wait_for_service_with_timeout({:host => host, :port => port, :timeout => timeout})
rescue SocketError
raise ServerDidntStart
end
end
true
end
end
25 changes: 25 additions & 0 deletions service_manager.gemspec
@@ -0,0 +1,25 @@
Gem::Specification.new do |s|
s.name = %q{service_manager}
s.version = "0.1"
s.authors = ["Tim Harper"]
s.date = Date.today.to_s
s.default_executable = %q{start_services}
s.description = <<-EOF
It launches and interacts with a set of services from a single terminal window.
* Colorizes output for each process to help distinguish them.
* Useful for integration-test applications where you need to start up several processes and test them all.
* Built because servolux wasn't working very well for me.
* Can detect exactly when a service is successfully launched by watching the output of the process.
EOF
s.email = ["tim@leadtune.com"]
s.executables = ["start_services"]
s.extra_rdoc_files = [
"MIT-LICENSE"
]
s.files = ["MIT-LICENSE"] + Dir["lib/**/*"]
s.homepage = %q{http://github.com/leadtune/service_manager}
s.require_paths = ["lib"]
s.summary = %q{service_manager}
s.test_files = []
end

0 comments on commit fcdd242

Please sign in to comment.