Compare commits

...
13 Commits
16 changed files with 121 additions and 168 deletions
+1
View File
@@ -1,3 +1,4 @@
.bundle
coverage coverage
example/log/* example/log/*
man/*.? man/*.?
+16
View File
@@ -0,0 +1,16 @@
source "http://rubygems.org"
group :development do
gem 'parka'
gem 'rake'
end
group :test do
gem 'fakefs', '~> 0.2.1'
gem 'rcov', '~> 0.9.8'
gem 'rr', '~> 0.10.11'
gem 'rspec', '~> 2.0.0.beta.19'
end
gem 'term-ansicolor', '~> 1.0.5'
gem 'thor', '~> 0.13.6'
+37
View File
@@ -0,0 +1,37 @@
GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.2)
fakefs (0.2.1)
mime-types (1.16)
parka (0.3.1)
rest-client
thor
rake (0.8.7)
rcov (0.9.8)
rest-client (1.6.0)
mime-types (>= 1.16)
rr (0.10.11)
rspec (2.0.0.beta.19)
rspec-core (= 2.0.0.beta.19)
rspec-expectations (= 2.0.0.beta.19)
rspec-mocks (= 2.0.0.beta.19)
rspec-core (2.0.0.beta.19)
rspec-expectations (2.0.0.beta.19)
diff-lcs (>= 1.1.2)
rspec-mocks (2.0.0.beta.19)
term-ansicolor (1.0.5)
thor (0.13.8)
PLATFORMS
ruby
DEPENDENCIES
fakefs (~> 0.2.1)
parka
rake
rcov (~> 0.9.8)
rr (~> 0.10.11)
rspec (~> 2.0.0.beta.19)
term-ansicolor (~> 1.0.5)
thor (~> 0.13.6)
+4 -38
View File
@@ -1,3 +1,7 @@
require "rubygems"
require "bundler"
Bundler.setup
require "rake" require "rake"
require "rspec" require "rspec"
require "rspec/core/rake_task" require "rspec/core/rake_task"
@@ -45,41 +49,3 @@ task :pages => :man do
git checkout master git checkout master
} }
end end
######################################################
begin
require 'jeweler'
Jeweler::Tasks.new do |s|
s.name = "foreman"
s.version = Foreman::VERSION
s.summary = "Process manager for applications with multiple components"
s.description = s.summary
s.author = "David Dollar"
s.email = "ddollar@gmail.com"
s.homepage = "http://github.com/ddollar/foreman"
s.platform = Gem::Platform::RUBY
s.has_rdoc = false
s.files = %w(Rakefile README.md) + Dir["{bin,export,lib,spec}/**/*"]
s.require_path = "lib"
# #s.bindir = "bin"
# s.executables = Dir["bin/*"]
s.default_executable = "foreman"
s.add_development_dependency 'fakefs', '~> 0.2.1'
s.add_development_dependency 'rake', '~> 0.8.7'
s.add_development_dependency 'rcov', '~> 0.9.8'
s.add_development_dependency 'rr', '~> 0.10.11'
s.add_development_dependency 'rspec', '~> 2.0.0'
s.add_dependency 'term-ansicolor', '~> 1.0.5'
s.add_dependency 'thor', '~> 0.13.6'
end
Jeweler::GemcutterTasks.new
rescue LoadError
puts "Jeweler not available. Install it with: sudo gem install jeweler"
end
+1 -1
View File
@@ -1,2 +1,2 @@
ticker ./ticker ticker ./ticker $PORT
error ./error error ./error
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
while true while true
puts "tick" puts "tick: #{ARGV.inspect}"
sleep 1 sleep 1
end end
+1 -1
View File
@@ -3,4 +3,4 @@ stop on stopping <%= app %>-<%= process.name %>
respawn respawn
chdir <%= engine.directory %> chdir <%= engine.directory %>
exec su <%= user %> -c "PORT=<%= port %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1" exec su - <%= user %> -c 'export PORT=<%= port %>; <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
+10 -86
View File
@@ -1,89 +1,13 @@
# Generated by jeweler require "rubygems"
# DO NOT EDIT THIS FILE DIRECTLY require "parka/specification"
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s| Parka::Specification.new do |gem|
s.name = %q{foreman} gem.name = "foreman"
s.version = "0.7.0" gem.version = Foreman::VERSION
gem.summary = "Process manager for applications with multiple components"
gem.homepage = "http://github.com/ddollar/foreman"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= gem.executables = "foreman"
s.authors = ["David Dollar"] gem.files << "man/foreman.1"
s.date = %q{2010-07-19} gem.files << Dir["export/**/*"]
s.default_executable = %q{foreman}
s.description = %q{Process manager for applications with multiple components}
s.email = %q{ddollar@gmail.com}
s.executables = ["foreman"]
s.extra_rdoc_files = [
"README.markdown"
]
s.files = [
"Rakefile",
"bin/foreman",
"export/upstart/master.conf.erb",
"export/upstart/process.conf.erb",
"export/upstart/process_master.conf.erb",
"lib/foreman.rb",
"lib/foreman/cli.rb",
"lib/foreman/engine.rb",
"lib/foreman/export.rb",
"lib/foreman/export/base.rb",
"lib/foreman/export/inittab.rb",
"lib/foreman/export/upstart.rb",
"lib/foreman/process.rb",
"lib/foreman/utils.rb",
"spec/foreman/cli_spec.rb",
"spec/foreman/engine_spec.rb",
"spec/foreman/export/upstart_spec.rb",
"spec/foreman/export_spec.rb",
"spec/foreman/process_spec.rb",
"spec/foreman_spec.rb",
"spec/spec_helper.rb"
]
s.homepage = %q{http://github.com/ddollar/foreman}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.7}
s.summary = %q{Process manager for applications with multiple components}
s.test_files = [
"spec/foreman/cli_spec.rb",
"spec/foreman/engine_spec.rb",
"spec/foreman/export/upstart_spec.rb",
"spec/foreman/export_spec.rb",
"spec/foreman/process_spec.rb",
"spec/foreman_spec.rb",
"spec/spec_helper.rb"
]
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_development_dependency(%q<fakefs>, ["~> 0.2.1"])
s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
s.add_development_dependency(%q<rcov>, ["~> 0.9.8"])
s.add_development_dependency(%q<rr>, ["~> 0.10.11"])
s.add_development_dependency(%q<rspec>, ["~> 2.0.0"])
s.add_runtime_dependency(%q<term-ansicolor>, ["~> 1.0.5"])
s.add_runtime_dependency(%q<thor>, ["~> 0.13.6"])
else
s.add_dependency(%q<fakefs>, ["~> 0.2.1"])
s.add_dependency(%q<rake>, ["~> 0.8.7"])
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
s.add_dependency(%q<rr>, ["~> 0.10.11"])
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
s.add_dependency(%q<term-ansicolor>, ["~> 1.0.5"])
s.add_dependency(%q<thor>, ["~> 0.13.6"])
end
else
s.add_dependency(%q<fakefs>, ["~> 0.2.1"])
s.add_dependency(%q<rake>, ["~> 0.8.7"])
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
s.add_dependency(%q<rr>, ["~> 0.10.11"])
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
s.add_dependency(%q<term-ansicolor>, ["~> 1.0.5"])
s.add_dependency(%q<thor>, ["~> 0.13.6"])
end
end end
+1 -1
View File
@@ -1,6 +1,6 @@
module Foreman module Foreman
VERSION = "0.7.0" VERSION = "0.7.5"
class AppDoesNotExist < Exception; end class AppDoesNotExist < Exception; end
+1
View File
@@ -9,6 +9,7 @@ class Foreman::CLI < Thor
desc "start [PROCESS]", "Start the application, or a specific process" desc "start [PROCESS]", "Start the application, or a specific process"
method_option :port, :type => :numeric, :aliases => "-p"
method_option :concurrency, :type => :string, :aliases => "-c", method_option :concurrency, :type => :string, :aliases => "-c",
:banner => '"alpha=5,bar=3"' :banner => '"alpha=5,bar=3"'
+28 -27
View File
@@ -20,27 +20,14 @@ class Foreman::Engine
@directory = File.expand_path(File.dirname(procfile)) @directory = File.expand_path(File.dirname(procfile))
end end
def processes(concurrency=nil) def processes
@processes ||= begin @processes ||= begin
concurrency = Foreman::Utils.parse_concurrency(concurrency)
procfile.split("\n").inject({}) do |hash, line| procfile.split("\n").inject({}) do |hash, line|
next if line.strip == "" next if line.strip == ""
name, command = line.split(" ", 2) name, command = line.split(" ", 2)
process = Foreman::Process.new(name, command)
if concurrency[name] > 1 then process.color = next_color
1.upto(concurrency[name]) do |num| hash.update(process.name => process)
process = Foreman::Process.new("#{name}.#{num}", command)
process.color = next_color
hash[process.name] = process
end
else
process = Foreman::Process.new(name, command)
process.color = next_color
hash[process.name] = process
end
hash
end end
end end
end end
@@ -48,8 +35,8 @@ class Foreman::Engine
def start(options={}) def start(options={})
proctitle "ruby: foreman master" proctitle "ruby: foreman master"
processes(options[:concurrency]).each do |name, process| processes.each do |name, process|
fork process fork process, options
end end
trap("TERM") { kill_and_exit("TERM") } trap("TERM") { kill_and_exit("TERM") }
@@ -59,11 +46,7 @@ class Foreman::Engine
end end
def execute(name, options={}) def execute(name, options={})
processes(options[:concurrency]).values.select do |process| fork processes[name], options
process.name =~ /\A#{name}\.?\d*\Z/
end.each do |process|
fork process
end
trap("TERM") { kill_and_exit("TERM") } trap("TERM") { kill_and_exit("TERM") }
trap("INT") { kill_and_exit("INT") } trap("INT") { kill_and_exit("INT") }
@@ -71,9 +54,25 @@ class Foreman::Engine
watch_for_termination watch_for_termination
end end
def port_for(process, num, base_port=nil)
base_port ||= 5000
offset = processes.keys.sort.index(process.name) * 100
base_port.to_i + offset + num - 1
end
private ###################################################################### private ######################################################################
def fork(process) def fork(process, options={})
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
1.upto(concurrency[process.name]) do |num|
fork_individual(process, port_for(process, num, options[:port]))
end
end
def fork_individual(process, port)
ENV["PORT"] = port.to_s
pid = Process.fork do pid = Process.fork do
run(process) run(process)
end end
@@ -98,6 +97,7 @@ private ######################################################################
rescue PTY::ChildExited, Interrupt rescue PTY::ChildExited, Interrupt
info "process exiting", process info "process exiting", process
end end
Process.waitall
end end
end end
@@ -107,6 +107,7 @@ private ######################################################################
info "killing #{process.name} in pid #{pid}" info "killing #{process.name} in pid #{pid}"
Process.kill(signal, pid) Process.kill(signal, pid)
end end
Process.waitall
exit 0 exit 0
end end
@@ -127,8 +128,8 @@ private ######################################################################
end end
def pad_process_name(process) def pad_process_name(process)
name = process ? process.name : "system" name = process ? "#{process.name}:#{ENV["PORT"]}" : "system"
name.ljust(longest_process_name) name.ljust(longest_process_name + 6) # add 6 for port padding
end end
def print_info def print_info
-6
View File
@@ -27,12 +27,6 @@ private ######################################################################
File.read(File.expand_path("../../../../export/#{name}", __FILE__)) File.read(File.expand_path("../../../../export/#{name}", __FILE__))
end end
def port_for(base_port, app, num)
base_port ||= 5000
offset = engine.processes.keys.sort.index(app) * 100
base_port.to_i + offset + num - 1
end
def write_file(filename, contents) def write_file(filename, contents)
say "writing: #{filename}" say "writing: #{filename}"
+2 -2
View File
@@ -7,7 +7,7 @@ class Foreman::Export::Inittab < Foreman::Export::Base
user = options[:user] || app user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}" log_root = options[:log] || "/var/log/#{app}"
concurrency = parse_concurrency(options[:concurrency]) concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
inittab = [] inittab = []
inittab << "# ----- foreman #{app} processes -----" inittab << "# ----- foreman #{app} processes -----"
@@ -15,7 +15,7 @@ class Foreman::Export::Inittab < Foreman::Export::Base
engine.processes.values.inject(1) do |index, process| engine.processes.values.inject(1) do |index, process|
1.upto(concurrency[process.name]) do |num| 1.upto(concurrency[process.name]) do |num|
id = app.slice(0, 2).upcase + sprintf("%02d", index) id = app.slice(0, 2).upcase + sprintf("%02d", index)
port = port_for(options[:port], process.name, num) port = engine.port_for(process, num, options[:port])
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'" inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
index += 1 index += 1
end end
+2 -2
View File
@@ -17,7 +17,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
FileUtils.rm(file) FileUtils.rm(file)
end end
concurrency = parse_concurrency(options[:concurrency]) concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
master_template = export_template("upstart/master.conf.erb") master_template = export_template("upstart/master.conf.erb")
master_config = ERB.new(master_template).result(binding) master_config = ERB.new(master_template).result(binding)
@@ -31,7 +31,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
1.upto(concurrency[process.name]) do |num| 1.upto(concurrency[process.name]) do |num|
port = port_for(options[:port], process.name, num) port = engine.port_for(process, num, options[:port])
process_config = ERB.new(process_template).result(binding) process_config = ERB.new(process_template).result(binding)
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
end end
+4
View File
@@ -29,6 +29,10 @@ The following options control how the application is run:
Specify the number of each process type to run. The value passed in Specify the number of each process type to run. The value passed in
should be in the format `process=num,process=num` should be in the format `process=num,process=num`
* `-p`, `--port`:
Specify which port to use as the base for this application. Should be
a multiple of 1000.
## EXPORTING ## EXPORTING
`foreman export` is used to export your application to another process `foreman export` is used to export your application to another process
+12 -3
View File
@@ -23,17 +23,26 @@ describe "Foreman::Engine" do
describe "start" do describe "start" do
it "forks the processes" do it "forks the processes" do
write_procfile write_procfile
mock(subject).fork(subject.processes["alpha"]) mock(subject).fork(subject.processes["alpha"], {})
mock(subject).fork(subject.processes["bravo"]) mock(subject).fork(subject.processes["bravo"], {})
mock(subject).watch_for_termination mock(subject).watch_for_termination
subject.start subject.start
end end
it "handles concurrency" do
write_procfile
mock(subject).fork_individual(subject.processes["alpha"], 5000)
mock(subject).fork_individual(subject.processes["alpha"], 5001)
mock(subject).fork_individual(subject.processes["bravo"], 5100)
mock(subject).watch_for_termination
subject.start(:concurrency => "alpha=2")
end
end end
describe "execute" do describe "execute" do
it "runs the processes" do it "runs the processes" do
write_procfile write_procfile
mock(subject).fork(subject.processes["alpha"]) mock(subject).fork(subject.processes["alpha"], {})
mock(subject).watch_for_termination mock(subject).watch_for_termination
subject.execute("alpha") subject.execute("alpha")
end end