service_manager 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tim Harper
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "service_manager"
4
+ ServiceManager.load_services ARGV[0]
5
+ ServiceManager.start
6
+ begin
7
+ sleep
8
+ rescue Interrupt => e
9
+ end
@@ -0,0 +1,59 @@
1
+ require 'tcpsocket-wait'
2
+ require 'background_process'
3
+ require 'net/http'
4
+
5
+ module ServiceManager
6
+ SERVICES_PATH = "./config/services.rb"
7
+
8
+ extend self
9
+
10
+ def services
11
+ @services ||= []
12
+ end
13
+
14
+ def load_services(path = nil)
15
+ path ||= SERVICES_PATH
16
+ return if @services_loaded
17
+ load path
18
+ @services_loaded = true
19
+ end
20
+
21
+ def define_service(name = nil, &block)
22
+ name ||= File.basename(caller.first.gsub(/.rb:.+$/, ""))
23
+ ServiceManager::Service.new(:name => name).tap do |service|
24
+ yield service
25
+ services << service
26
+ end
27
+ end
28
+
29
+ def services_hash
30
+ Hash[ServiceManager.services.map { |s| [s.name.to_sym, s]}]
31
+ end
32
+
33
+ def stop(which = :all)
34
+ puts "Stopping the services..."
35
+ services.map {|s| Thread.new { s.stop } }.map(&:join)
36
+ end
37
+
38
+ def start(which = :all)
39
+ load_services
40
+ raise RuntimeError, "No services defined" if services.empty?
41
+ threads = services.map do |s|
42
+ Thread.new do
43
+ begin
44
+ s.start
45
+ rescue ServiceManager::Service::ServerDidntStart
46
+ puts "Quitting due to failure."
47
+ exit(1)
48
+ rescue Exception => e
49
+ puts e
50
+ puts e.backtrace
51
+ exit(1)
52
+ end
53
+ end
54
+ end
55
+ threads.map(&:join)
56
+ end
57
+ end
58
+
59
+ require "service_manager/service"
@@ -0,0 +1,118 @@
1
+ class ServiceManager::Service
2
+ NORMAL_COLOR = 37
3
+
4
+ attr_accessor :name, :host, :port, :cwd, :reload_uri, :start_cmd, :process, :loaded_cue, :timeout, :color
5
+
6
+ class ServerDidntStart < Exception; end
7
+
8
+ def initialize(options = {})
9
+ options.each { |k,v| send("#{k}=", v) }
10
+ self.host ||= "localhost"
11
+ self.color ||= NORMAL_COLOR
12
+ self.timeout ||= 30
13
+ raise ArgumentError, "You need to provide a name for this app service" unless name
14
+ end
15
+
16
+ def url
17
+ "http://#{host}:#{port}"
18
+ end
19
+
20
+ def server_info_hash
21
+ {:name => name, :host => host, :port => port}
22
+ end
23
+
24
+ def watch_for_cue
25
+ process.detect(:both, timeout) do |output|
26
+ STDOUT << colorize(output)
27
+ output =~ loaded_cue
28
+ end
29
+ end
30
+
31
+ def start_output_stream_thread
32
+ Thread.new { process.detect { |output| STDOUT << colorize(output); nil} }
33
+ end
34
+
35
+ def start_cmd
36
+ @start_cmd.is_a?(Proc) ? instance_eval(&@start_cmd) : @start_cmd
37
+ end
38
+
39
+ def without_bundler_env(&block)
40
+ vars = %w{BUNDLE_PATH BUNDLE_GEMFILE BUNDLE_BIN_PATH}
41
+ old_values = vars.map {|v| ENV.delete(v)}
42
+ yield
43
+ vars.zip(old_values).each { |var, value| ENV[var] = value }
44
+ end
45
+
46
+ def start
47
+ if running?
48
+ puts "Server for #{colorized_service_name} detected as running."
49
+ reload || puts("Reloading not supported. Any changes made to code for #{colorized_service_name} will not take effect!")
50
+ return false
51
+ end
52
+
53
+ puts "Starting #{colorized_service_name} in #{cwd} with '#{start_cmd}'"
54
+ Dir.chdir(cwd) do
55
+ without_bundler_env do
56
+ # system("bash -c set")
57
+ self.process = PTYBackgroundProcess.run(start_cmd)
58
+ end
59
+ end
60
+ at_exit { stop }
61
+ wait
62
+ puts "Server #{colorized_service_name} is up."
63
+ end
64
+
65
+ def stop
66
+ return unless process
67
+ puts "Shutting down #{colorized_service_name}"
68
+ process.kill
69
+ process.wait(3)
70
+ if process.running?
71
+ process.kill("KILL") # ok... no more Mr. Nice Guy.
72
+ process.wait
73
+ end
74
+ puts "Server #{colorized_service_name} (#{process.pid}) is shut down"
75
+ self.process = nil
76
+ true
77
+ end
78
+
79
+ def reload
80
+ return false unless reload_uri
81
+ puts "Reloading #{colorized_service_name} app by hitting http://#{host}:#{port}#{reload_uri} ..."
82
+ res = Net::HTTP.start(host, port) {|http| http.get(reload_uri) }
83
+ 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
84
+ true
85
+ end
86
+
87
+ def running?
88
+ TCPSocket.listening_service?(:port => port, :host => host)
89
+ end
90
+
91
+ protected
92
+ def colorize(output)
93
+ "\e[0;#{color}m#{output}\e[0;#{NORMAL_COLOR}m"
94
+ end
95
+
96
+ def colorized_service_name
97
+ if process
98
+ colorize("#{name} (#{process.pid})")
99
+ else
100
+ colorize("#{name}")
101
+ end
102
+ end
103
+
104
+ def wait
105
+ if loaded_cue
106
+ raise(ServerDidntStart) unless watch_for_cue
107
+ start_output_stream_thread
108
+ else
109
+ start_output_stream_thread
110
+ begin
111
+ TCPSocket.wait_for_service_with_timeout({:host => host, :port => port, :timeout => timeout})
112
+ rescue SocketError
113
+ raise ServerDidntStart
114
+ end
115
+ end
116
+ true
117
+ end
118
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_manager
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Tim Harper
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-17 00:00:00 -06:00
18
+ default_executable: start_services
19
+ dependencies: []
20
+
21
+ description: |
22
+ It launches and interacts with a set of services from a single terminal window.
23
+
24
+ * Colorizes output for each process to help distinguish them.
25
+ * Useful for integration-test applications where you need to start up several processes and test them all.
26
+ * Built because servolux wasn't working very well for me.
27
+ * Can detect exactly when a service is successfully launched by watching the output of the process.
28
+
29
+ email:
30
+ - tim@leadtune.com
31
+ executables:
32
+ - start_services
33
+ extensions: []
34
+
35
+ extra_rdoc_files:
36
+ - MIT-LICENSE
37
+ files:
38
+ - MIT-LICENSE
39
+ - lib/service_manager/service.rb
40
+ - lib/service_manager.rb
41
+ - bin/start_services
42
+ has_rdoc: true
43
+ homepage: http://github.com/leadtune/service_manager
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.7
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: service_manager
76
+ test_files: []
77
+