magistrate 0.1.0

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/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Magistrate
2
+
3
+ ## Installation
4
+
5
+ gem install magistrate
6
+
7
+ ## Description
8
+
9
+ Magistrate is a manager for workers that is cluster and network aware.
10
+ It interacts with a centralized server to get its management and marching orders.
11
+ It's designed to be a process manager that runs entirely in userspace: no root access needed.
12
+
13
+ ## Manual
14
+
15
+ The magistrate command line tool utilizes the following from the filesystem:
16
+
17
+ * config/magistrate.yml - The configuration file (override the path with the --config option)
18
+ * tmp/pids - stores the pids of itself and all managed workers
19
+
20
+ These are meant to coincide with easy running from a Rails app root (so that the worker config can be kept together with the app)
21
+ If you're using capistrano, then the tmp/pids directory is persisted across deploys, so Magistrate will continue to run
22
+ (with an updated config) even after a deploy.
23
+
24
+ Your user-space cron job should look like this:
25
+
26
+ */5 0 0 0 0 magistrate run --config ~/my_app/current/config/magistrate.yml
27
+
28
+ ### What if the server is down?
29
+
30
+ The magistrate request will time out after 30 seconds and then use its previously stored target_states.yml file
31
+
32
+ ## Command line options
33
+
34
+ --config path/to/config.yml
35
+
36
+ Sets the config file path. See example_config.yml for an example config
37
+
38
+ ### run
39
+
40
+ `magistrate run`
41
+
42
+ run is the primary command used. It's intended to be run as a cron job periodically. Each time it's run it'll:
43
+
44
+ * Download the target state for each worker from the server
45
+ * Check each worker
46
+ * Try to get it to its target state
47
+ * POST back to the server its state
48
+
49
+ ### list
50
+
51
+ `magistrate list`
52
+
53
+ Will return a string like:
54
+
55
+ {:test1=>{:state=>:unmonitored, :target_state=>:running}}
56
+
57
+ This is the status string that is sent to the remote server during a run
58
+
59
+ ### start / stop
60
+
61
+ `magistrate start WORKER_NAME`
62
+ `magistrate stop WORKER_NAME`
63
+
64
+ Allows you to manually start/stop a worker process. This has the side effect of writing the new target_state to the cached target state. It will
65
+ then continue to use this requested state UNTIL it next gets a different state from the server.
66
+
67
+ ## License
68
+
69
+ Copyright (C) 2011 by Drew Blas <drew.blas@gmail.com>
70
+
71
+ Permission is hereby granted, free of charge, to any person obtaining a copy
72
+ of this software and associated documentation files (the "Software"), to deal
73
+ in the Software without restriction, including without limitation the rights
74
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
75
+ copies of the Software, and to permit persons to whom the Software is
76
+ furnished to do so, subject to the following conditions:
77
+
78
+ The above copyright notice and this permission notice shall be included in
79
+ all copies or substantial portions of the Software.
80
+
81
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
82
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
83
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
84
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
85
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
86
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
87
+
88
+ ## Attribution
89
+
90
+ Inspiration and thanks to:
91
+
92
+ * foreman
93
+ * resque
94
+ * god
data/bin/magistrate ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "magistrate"
6
+
7
+ require "optparse"
8
+
9
+ action = :start
10
+ config_file = nil
11
+
12
+ ARGV.options do |opts|
13
+ opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} COMMAND [OPTIONS]"
14
+
15
+ opts.separator "COMMAND: run, list, start WORKER, stop WORKER"
16
+
17
+ opts.separator "Specific Options:"
18
+
19
+ opts.separator "Common Options:"
20
+
21
+ opts.on( "-h", "--help",
22
+ "Show this message." ) do
23
+ puts opts
24
+ exit
25
+ end
26
+
27
+ opts.on( '-c', '--config FILE', String, 'Specify Config file') do |f|
28
+ config_file = f
29
+ end
30
+
31
+ begin
32
+ opts.parse!
33
+ rescue
34
+ puts opts
35
+ exit
36
+ end
37
+ end
38
+
39
+ config_file ||= File.join('config', 'magistrate.yaml')
40
+
41
+ ARGV[0] ||= 'run'
42
+
43
+ Magistrate::Supervisor.new(config_file).send(ARGV[0], ARGV[1])
@@ -0,0 +1,13 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ keys.each do |key|
4
+ self[(key.to_sym rescue key) || key] = delete(key)
5
+ end
6
+ self
7
+ end
8
+
9
+ def recursive_symbolize_keys!
10
+ symbolize_keys!
11
+ values.select { |v| v.is_a?(Hash) }.each { |h| h.recursive_symbolize_keys! }
12
+ end
13
+ end
@@ -0,0 +1,246 @@
1
+ class Magistrate::Process
2
+
3
+ attr_reader :name, :daemonize, :start_cmd, :stop_cmd, :pid_file, :working_dir, :env
4
+ attr_accessor :target_state, :monitored
5
+
6
+ def initialize(name, options = {})
7
+ @name = name
8
+ @daemonize = options[:daemonize]
9
+ @working_dir = options[:working_dir]
10
+ @start_cmd = options[:start_cmd]
11
+
12
+ if @daemonize
13
+ @pid_file = File.join('tmp', 'pids', "#{@name}.pid")
14
+ @stop_signal = options[:stop_signal] || 'TERM'
15
+ else
16
+ @stop_cmd = options[:end_cmd]
17
+ @pid_file = options[:pid_file]
18
+ end
19
+
20
+ @stop_timeout = 5
21
+ @start_timeout = 5
22
+
23
+ @env = {}
24
+
25
+ @target_state = :unknown
26
+ end
27
+
28
+ def running?
29
+ end
30
+
31
+ def state
32
+ if @target_state == :unmonitored || @target_state == :unknown
33
+ :unmonitored
34
+ else
35
+ if self.alive?
36
+ :running
37
+ else
38
+ :stopped
39
+ end
40
+ end
41
+ end
42
+
43
+ # This is to be called when we first start managing a worker
44
+ # It will check if the pid exists and if so, is the process responding OK?
45
+ # It will take action based on the target state
46
+ def supervise!
47
+ LOGGER.info("#{@name} supervising. Is: #{state}. Target: #{@target_state}")
48
+ if state != @target_state
49
+ if @target_state == :running
50
+ start
51
+ elsif @target_state == :stopped
52
+ stop
53
+ end
54
+ end
55
+ end
56
+
57
+ def start
58
+ LOGGER.info("#{@name} starting")
59
+ if @daemonize
60
+ @pid = double_fork(@start_cmd)
61
+ # TODO: Should check if the pid really exists as we expect
62
+ write_pid
63
+ else
64
+ @pid = single_fork(@start_cmd)
65
+ end
66
+ @pid
67
+ end
68
+
69
+ def stop
70
+ if @daemonize
71
+ signal(@stop_signal, pid)
72
+
73
+ # Poll to see if it's dead
74
+ @stop_timeout.times do
75
+ begin
76
+ ::Process.kill(0, pid)
77
+ rescue Errno::ESRCH
78
+ # It died. Good.
79
+ LOGGER.info("#{@name} process stopped")
80
+ return
81
+ end
82
+
83
+ sleep 1
84
+ end
85
+
86
+ signal('KILL', pid)
87
+ LOGGER.warn("#{@name} still alive after #{@stop_timeout}s; sent SIGKILL")
88
+ else
89
+ single_fork(@stop_cmd)
90
+ ensure_stop
91
+ end
92
+ end
93
+
94
+ # single fork self-daemonizing processes
95
+ # we want to wait for them to finish
96
+ def single_fork(command)
97
+ pid = self.spawn(command)
98
+ status = ::Process.waitpid2(pid, 0)
99
+ exit_code = status[1] >> 8
100
+
101
+ if exit_code != 0
102
+ LOGGER.warn("#{@name} command exited with non-zero code = #{exit_code}")
103
+ end
104
+ pid
105
+ end
106
+
107
+ def double_fork(command)
108
+ pid = nil
109
+ # double fork daemonized processes
110
+ # we don't want to wait for them to finish
111
+ r, w = IO.pipe
112
+ begin
113
+ opid = fork do
114
+ STDOUT.reopen(w)
115
+ r.close
116
+ pid = self.spawn(command)
117
+ puts pid.to_s # send pid back to forker
118
+ end
119
+
120
+ ::Process.waitpid(opid, 0)
121
+ w.close
122
+ pid = r.gets.chomp
123
+ ensure
124
+ # make sure the file descriptors get closed no matter what
125
+ r.close rescue nil
126
+ w.close rescue nil
127
+ end
128
+
129
+ pid
130
+ end
131
+
132
+ # Fork/exec the given command, returns immediately
133
+ # +command+ is the String containing the shell command
134
+ #
135
+ # Returns nothing
136
+ def spawn(command)
137
+ fork do
138
+ ::Process.setsid
139
+
140
+ dir = @working_dir || '/'
141
+ Dir.chdir dir
142
+
143
+ $0 = command
144
+ STDIN.reopen "/dev/null"
145
+
146
+ STDOUT.reopen '/dev/null'
147
+ STDERR.reopen STDOUT
148
+
149
+ # if self.log_cmd
150
+ # STDOUT.reopen IO.popen(self.log_cmd, "a")
151
+ # else
152
+ # STDOUT.reopen file_in_chroot(self.log), "a"
153
+ # end
154
+ #
155
+ # if err_log_cmd
156
+ # STDERR.reopen IO.popen(err_log_cmd, "a")
157
+ # elsif err_log && (log_cmd || err_log != log)
158
+ # STDERR.reopen file_in_chroot(err_log), "a"
159
+ # else
160
+ # STDERR.reopen STDOUT
161
+ # end
162
+
163
+ # close any other file descriptors
164
+ 3.upto(256){|fd| IO::new(fd).close rescue nil}
165
+
166
+ if @env && @env.is_a?(Hash)
167
+ @env.each do |key, value|
168
+ ENV[key] = value.to_s
169
+ end
170
+ end
171
+
172
+ exec command unless command.empty?
173
+ end
174
+ end
175
+
176
+ # Ensure that a stop command actually stops the process. Force kill
177
+ # if necessary.
178
+ #
179
+ # Returns nothing
180
+ def ensure_stop
181
+ LOGGER.warn("#{@name} ensuring stop...")
182
+
183
+ unless self.pid
184
+ LOGGER.warn("#{@name} stop called but pid is uknown")
185
+ return
186
+ end
187
+
188
+ # Poll to see if it's dead
189
+ @stop_timeout.times do
190
+ begin
191
+ signal(0)
192
+ rescue Errno::ESRCH
193
+ # It died. Good.
194
+ return
195
+ end
196
+
197
+ sleep 1
198
+ end
199
+
200
+ # last resort
201
+ signal('KILL')
202
+ LOGGER.warn("#{@name} still alive after #{@stop_timeout}s; sent SIGKILL")
203
+ end
204
+
205
+ # Send the given signal to this process.
206
+ #
207
+ # Returns nothing
208
+ def signal(sig, target_pid = nil)
209
+ target_pid ||= self.pid
210
+ sig = sig.to_i if sig.to_i != 0
211
+ LOGGER.info("#{@name} sending signal '#{sig}' to pid #{self.pid}")
212
+ ::Process.kill(sig, target_pid) rescue nil
213
+ end
214
+
215
+ # Fetch the PID from pid_file. If the pid_file does not
216
+ # exist, then use the PID from the last time it was read.
217
+ # If it has never been read, then return nil.
218
+ #
219
+ # Returns Integer(pid) or nil
220
+ def pid
221
+ contents = File.read(@pid_file).strip rescue ''
222
+ real_pid = contents =~ /^\d+$/ ? contents.to_i : nil
223
+
224
+ if real_pid
225
+ @pid = real_pid
226
+ real_pid
227
+ else
228
+ @pid
229
+ end
230
+ end
231
+
232
+ def write_pid
233
+ File.open(@pid_file, 'w') do |f|
234
+ f.write @pid
235
+ end
236
+ end
237
+
238
+ def alive?
239
+ if p = self.pid
240
+ !!::Process.kill(0, p) rescue false
241
+ else
242
+ false
243
+ end
244
+ end
245
+
246
+ end
@@ -0,0 +1,159 @@
1
+ require 'rubygems'
2
+ # Ugh, yaml for local serialization and json for over-the-wire serialization sucks.
3
+ # But YAML doesn't go over the wire well and json doesn't support comments and makes for really bad local config files
4
+ # Almost makes me want to use XML ;)
5
+ require 'yaml'
6
+ require 'json'
7
+ require 'fileutils'
8
+ require 'net/http'
9
+ require 'uri'
10
+
11
+ module Magistrate
12
+ class Supervisor
13
+ def initialize(config_file)
14
+ @workers = {}
15
+
16
+ #File.expand_path('~')
17
+ @pid_path = File.join( 'tmp', 'pids' )
18
+
19
+ FileUtils.mkdir_p(@pid_path) unless File.directory? @pid_path
20
+
21
+ @config = File.open(config_file) { |file| YAML.load(file) }
22
+ @config.recursive_symbolize_keys!
23
+
24
+ @uri = URI.parse @config[:monitor_url]
25
+
26
+ @config[:workers].each do |k,v|
27
+ @workers[k] = Process.new(k,v)
28
+ end
29
+
30
+ @loaded_from = nil
31
+ end
32
+
33
+ def run(params = nil)
34
+ puts "Starting Magistrate [[[#{self.name}]]] talking to [[[#{@config[:monitor_url]}]]]"
35
+ set_target_states!
36
+
37
+ # Pull in all already-running workers and set their target states
38
+ @workers.each do |k, worker|
39
+ worker.supervise!
40
+ end
41
+
42
+ send_status
43
+ end
44
+
45
+ #
46
+ def start(params = nil)
47
+ worker = params
48
+ puts "Starting: #{worker}"
49
+ @workers[worker.to_sym].supervise!
50
+
51
+ # Save that we've requested this to be started
52
+ end
53
+
54
+ def stop(params = nil)
55
+ worker = params
56
+ puts "Stopping: #{worker}"
57
+ @workers[worker.to_sym].stop
58
+
59
+ # Save that we've requested this to be stopped
60
+ end
61
+
62
+ def list(params = nil)
63
+ set_target_states!
64
+
65
+ require 'pp'
66
+ pp status
67
+ end
68
+
69
+ # Returns the actual hash of all workers and their status
70
+ def status
71
+ s = {}
72
+
73
+ @workers.each do |k,process|
74
+ s[k] = {
75
+ :state => process.state,
76
+ :target_state => process.target_state
77
+ }
78
+ end
79
+
80
+ s
81
+ end
82
+
83
+ protected
84
+
85
+ # Loads the @target_states from either the remote server or local cache
86
+ # Then sets all the worker target_states to the loaded values
87
+ def set_target_states!(local_only = false)
88
+ local_only ? load_saved_target_states! : load_remote_target_states!
89
+
90
+ if @target_states && @target_states['workers']
91
+ @target_states['workers'].each do |name, target|
92
+ name = name.to_sym
93
+ if @workers[name]
94
+ @workers[name].target_state = target['target_state'].to_sym if target['target_state']
95
+ else
96
+ puts "Worker #{name} not found in local config"
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ # Gets and sets @target_states from the server
103
+ # Automatically falls back to the local cache
104
+ def load_remote_target_states!
105
+ http = Net::HTTP.new(@uri.host, @uri.port)
106
+ http.read_timeout = 30
107
+ request = Net::HTTP::Get.new(@uri.request_uri + "api/status/#{self.name}")
108
+
109
+ response = http.request(request)
110
+
111
+ if response.code == '200'
112
+ @loaded_from = :server
113
+ @target_states = JSON.parse(response.body)
114
+ save_target_states! # The double serialization here might not be best for performance, but will guarantee that the locally stored file is internally consistent
115
+ else
116
+ puts "Server responded with error #{response.code} : [[[#{response.body}]]]. Using saved target states..."
117
+ load_saved_target_states!
118
+ end
119
+
120
+ rescue StandardError => e
121
+ puts "Connection to server #{@config[:monitor_url]} failed."
122
+ puts "Error: #{e}"
123
+ puts "Using saved target states..."
124
+ load_saved_target_states!
125
+ end
126
+
127
+ # Loads the @target_states variable from the cache file
128
+ def load_saved_target_states!
129
+ @loaded_from = :file
130
+ @target_states = File.open(target_states_file) { |file| YAML.load(file) }
131
+ end
132
+
133
+ def save_target_states!
134
+ File.open(target_states_file, "w") { |file| YAML.dump(@target_states, file) }
135
+ end
136
+
137
+ def target_states_file
138
+ File.join @pid_path, 'target_states.yml'
139
+ end
140
+
141
+ # Sends the current system status back to the server
142
+ # Currently only sends basic worker info, but could start sending lots more:
143
+ #
144
+ def send_status
145
+ http = Net::HTTP.new(@uri.host, @uri.port)
146
+ http.read_timeout = 30
147
+ request = Net::HTTP::Post.new(@uri.request_uri + "api/status/#{self.name}")
148
+ request.set_form_data({ :status => JSON.generate(status) })
149
+ response = http.request(request)
150
+ rescue StandardError => e
151
+ puts "Sending status to #{@config[:monitor_url]} failed"
152
+ end
153
+
154
+ # This is the name that the magistrate_monitor will identify us as
155
+ def name
156
+ @_name ||= (@config[:supervisor_name_override] || "#{`hostname`.chomp}-#{`whoami`.chomp}").gsub(/[^a-zA-Z0-9\-\_]/, ' ').gsub(/\s+/, '-').downcase
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,3 @@
1
+ module Magistrate
2
+ VERSION = "0.1.0"
3
+ end
data/lib/magistrate.rb ADDED
@@ -0,0 +1,14 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'magistrate/core_ext'
4
+ require 'magistrate/version'
5
+ require 'magistrate/supervisor'
6
+ require 'magistrate/process'
7
+
8
+ require 'logger'
9
+ # App wide logging system
10
+ LOGGER = Logger.new(STDOUT)
11
+
12
+ module Magistrate
13
+
14
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+ require "magistrate/process"
3
+
4
+ describe "Magistrate::Process" do
5
+ describe 'Rake-Like Worker' do
6
+ before(:each) do
7
+ @process = Magistrate::Process.new(
8
+ :name => 'rake_like_worker',
9
+ :daemonize => true,
10
+ :start_cmd => 'ruby spec/resources/rake_like_worker.rb'
11
+ )
12
+
13
+ stub(@process).spawn do
14
+ raise "Unexpected spawn call made...you don't want your specs actually spawning stuff, right?"
15
+ end
16
+ end
17
+
18
+ describe 'state' do
19
+ it 'should be unmonitored by default' do
20
+ @process.state.should == :unmonitored
21
+ end
22
+
23
+ it 'should be unmonitored when unmonitored is the target state' do
24
+ @process.target_state = :unmonitored
25
+ @process.state.should == :unmonitored
26
+ end
27
+
28
+ it 'should be stopped when target state other that unmonitored' do
29
+ @process.target_state = :foo
30
+ @process.state.should == :stopped
31
+ end
32
+ end
33
+
34
+ it "should " do
35
+ true
36
+ end
37
+ end
38
+
39
+ describe 'Self-Daemonizing Worker' do
40
+
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+ require "magistrate/supervisor"
3
+
4
+ describe "Magistrate::Supervisor" do
5
+ before(:each) do
6
+ @supervisor = Magistrate::Supervisor.new("spec/resources/example.yml")
7
+ end
8
+
9
+ it "should initialize correctly" do
10
+ lambda { Magistrate::Supervisor.new("spec/resources/example.yml") }.should_not raise_error
11
+ end
12
+
13
+ it "should show basic status for its workers" do
14
+ @supervisor.status.should == { :daemon_worker=>{:state=>:unmonitored, :target_state=>:unknown},
15
+ :rake_like_worker=>{:state=>:unmonitored, :target_state=>:unknown} }
16
+ end
17
+
18
+ # it 'should'
19
+ end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+ require "magistrate"
3
+
4
+ describe Magistrate do
5
+
6
+ describe "VERSION" do
7
+ subject { Magistrate::VERSION }
8
+ it { should be_a String }
9
+ end
10
+
11
+ end
@@ -0,0 +1,23 @@
1
+ monitor_url: http://localhost
2
+
3
+ #Normal magistrate reports itself as: `hostname`-`user`
4
+ #supervisor_name_override: some_other_name
5
+
6
+ workers:
7
+ # If daemonize is true, then Magistrate will daemonize this process (it doesn't daemonize itself)
8
+ # Magistrate will track the pid of the underlying process
9
+ # And will stop it by killing the pid
10
+ # It will ping the status by sending USR1 signal to the process
11
+ rake_like_worker:
12
+ daemonize: true
13
+ working_dir: /data/app/
14
+ start_cmd: rake my:task RAILS_ENV=production
15
+
16
+ # If daemonize is false, then Magistrate will use a separate start and stop command
17
+ # You must also manually specify the pid that this daemonized process creates
18
+ daemon_worker:
19
+ daemonize: false
20
+ working_dir: /data/app/
21
+ start_cmd: mongrel_rails start -d
22
+ stop_cmd: mongrel_rails stop
23
+ pid_file: tmp/pids/mongrel.pid
@@ -0,0 +1,10 @@
1
+ trap('USR1') {
2
+ puts 'Doing good'
3
+ }
4
+
5
+ while true do
6
+ puts '1'
7
+ sleep(3)
8
+ end
9
+
10
+
@@ -0,0 +1,43 @@
1
+ require "rubygems"
2
+ require "rspec"
3
+ #require "fakefs/safe"
4
+ #require "fakefs/spec_helpers"
5
+
6
+ $:.unshift File.expand_path("../../lib", __FILE__)
7
+
8
+ def mock_error(subject, message)
9
+ mock_exit do
10
+ mock(subject).puts("ERROR: #{message}")
11
+ yield
12
+ end
13
+ end
14
+
15
+ def mock_exit(&block)
16
+ block.should raise_error(SystemExit)
17
+ end
18
+ #
19
+ # def load_export_templates_into_fakefs(type)
20
+ # FakeFS.deactivate!
21
+ # files = Dir[File.expand_path("../../data/export/#{type}/**", __FILE__)].inject({}) do |hash, file|
22
+ # hash.update(file => File.read(file))
23
+ # end
24
+ # FakeFS.activate!
25
+ # files.each do |filename, contents|
26
+ # File.open(filename, "w") do |f|
27
+ # f.puts contents
28
+ # end
29
+ # end
30
+ # end
31
+ #
32
+ # def example_export_file(filename)
33
+ # FakeFS.deactivate!
34
+ # data = File.read(File.expand_path("../resources/export/#{filename}", __FILE__))
35
+ # FakeFS.activate!
36
+ # data
37
+ # end
38
+
39
+ RSpec.configure do |config|
40
+ config.color_enabled = true
41
+ #config.include FakeFS::SpecHelpers
42
+ config.mock_with :rr
43
+ end
metadata ADDED
@@ -0,0 +1,200 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: magistrate
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Drew Blas
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-16 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rake
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: ronn
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: fakefs
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ hash: 21
72
+ segments:
73
+ - 0
74
+ - 2
75
+ - 1
76
+ version: 0.2.1
77
+ type: :development
78
+ version_requirements: *id004
79
+ - !ruby/object:Gem::Dependency
80
+ name: rcov
81
+ prerelease: false
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ hash: 43
88
+ segments:
89
+ - 0
90
+ - 9
91
+ - 8
92
+ version: 0.9.8
93
+ type: :development
94
+ version_requirements: *id005
95
+ - !ruby/object:Gem::Dependency
96
+ name: rr
97
+ prerelease: false
98
+ requirement: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ hash: 19
104
+ segments:
105
+ - 1
106
+ - 0
107
+ - 2
108
+ version: 1.0.2
109
+ type: :development
110
+ version_requirements: *id006
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ prerelease: false
114
+ requirement: &id007 !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ~>
118
+ - !ruby/object:Gem::Version
119
+ hash: 23
120
+ segments:
121
+ - 2
122
+ - 6
123
+ - 0
124
+ version: 2.6.0
125
+ type: :development
126
+ version_requirements: *id007
127
+ - !ruby/object:Gem::Dependency
128
+ name: webmock
129
+ prerelease: false
130
+ requirement: &id008 !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ~>
134
+ - !ruby/object:Gem::Version
135
+ hash: 7
136
+ segments:
137
+ - 1
138
+ - 6
139
+ - 4
140
+ version: 1.6.4
141
+ type: :development
142
+ version_requirements: *id008
143
+ description: Cluster-based process / worker manager
144
+ email: drew.blas@gmail.com
145
+ executables:
146
+ - magistrate
147
+ extensions: []
148
+
149
+ extra_rdoc_files: []
150
+
151
+ files:
152
+ - bin/magistrate
153
+ - lib/magistrate/core_ext.rb
154
+ - lib/magistrate/process.rb
155
+ - lib/magistrate/supervisor.rb
156
+ - lib/magistrate/version.rb
157
+ - lib/magistrate.rb
158
+ - README.md
159
+ - spec/magistrate/process_spec.rb
160
+ - spec/magistrate/supervisor_spec.rb
161
+ - spec/magistrate_spec.rb
162
+ - spec/resources/example.yml
163
+ - spec/resources/rake_like_worker.rb
164
+ - spec/spec_helper.rb
165
+ has_rdoc: true
166
+ homepage: http://github.com/drewblas/magistrate
167
+ licenses: []
168
+
169
+ post_install_message:
170
+ rdoc_options: []
171
+
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ none: false
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ hash: 3
180
+ segments:
181
+ - 0
182
+ version: "0"
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ none: false
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ hash: 3
189
+ segments:
190
+ - 0
191
+ version: "0"
192
+ requirements: []
193
+
194
+ rubyforge_project:
195
+ rubygems_version: 1.6.2
196
+ signing_key:
197
+ specification_version: 3
198
+ summary: Cluster-based process / worker manager
199
+ test_files: []
200
+