This commit is contained in:
David Dollar committed 2011-12-08 17:53:13 -08:00
1 parent c9411cd2b1
commit 5436b68cf1
13 files changed
+72 -79

No files matched your search

-3
View File
@@ -1,5 +1,2 @@
#!/bin/sh #!/bin/sh
echo "command[$*][$1]"
exec $1 2>&1 exec $1 2>&1
+2 -2
View File
@@ -3,7 +3,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
app.uid = "<%= user %>" app.uid = "<%= user %>"
app.gid = "<%= user %>" app.gid = "<%= user %>"
<% engine.processes.each do |process| %> <% engine.procfile.entries.each do |process| %>
<% 1.upto(concurrency[process.name]) do |num| %> <% 1.upto(concurrency[process.name]) do |num| %>
<% port = engine.port_for(process, num, options[:port]) %> <% port = engine.port_for(process, num, options[:port]) %>
app.process("<%= process.name %>-<%=num%>") do |process| app.process("<%= process.name %>-<%=num%>") do |process|
@@ -19,7 +19,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
process.monitor_children do |children| process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}" children.stop_command "kill -QUIT {{PID}}"
end end
process.group = "<%= app %>-<%= process.name %>" process.group = "<%= app %>-<%= process.name %>"
end end
<% end %> <% end %>
+5
View File
@@ -9,5 +9,10 @@ module Foreman
require 'foreman/engine' require 'foreman/engine'
Foreman::Engine.load_env!(env_file) Foreman::Engine.load_env!(env_file)
end end
def self.runner
File.expand_path("../../bin/runner", __FILE__)
end
end end
+5 -10
View File
@@ -8,20 +8,15 @@ class Foreman::CLI < Thor
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile" class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
desc "start [PROCESS]", "Start the application, or a specific process" desc "start", "Start the application"
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env" method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :port, :type => :numeric, :aliases => "-p" method_option :port, :type => :numeric, :aliases => "-p"
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"' method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
def start(process=nil) def start
check_procfile! check_procfile!
engine.start
if process
engine.execute(process)
else
engine.start
end
end end
desc "export FORMAT LOCATION", "Export the application to another process management format" desc "export FORMAT LOCATION", "Export the application to another process management format"
@@ -55,8 +50,8 @@ class Foreman::CLI < Thor
desc "check", "Validate your application's Procfile" desc "check", "Validate your application's Procfile"
def check def check
error "no processes defined" unless engine.processes.length > 0 error "no processes defined" unless engine.procfile.entries.length > 0
display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})" display "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
end end
private ###################################################################### private ######################################################################
+5 -2
View File
@@ -57,16 +57,19 @@ private ######################################################################
procfile.entries.each do |entry| procfile.entries.each do |entry|
reader, writer = IO.pipe reader, writer = IO.pipe
entry.spawn(concurrency[entry.name], writer, @directory, @environment).each do |process| entry.spawn(concurrency[entry.name], writer, @directory, @environment, base_port).each do |process|
running_processes[process.pid] = process running_processes[process.pid] = process
readers[process] = reader readers[process] = reader
end end
end end
end end
def base_port
options[:port] || 5000
end
def kill_all(signal="SIGTERM") def kill_all(signal="SIGTERM")
running_processes.each do |pid, process| running_processes.each do |pid, process|
p [:killing, pid]
Process.kill(signal, pid) rescue Errno::ESRCH Process.kill(signal, pid) rescue Errno::ESRCH
end end
end end
+1 -1
View File
@@ -12,7 +12,7 @@ class Foreman::Export::Inittab < Foreman::Export::Base
inittab = [] inittab = []
inittab << "# ----- foreman #{app} processes -----" inittab << "# ----- foreman #{app} processes -----"
engine.processes.inject(1) do |index, process| engine.procfile.entries.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 = engine.port_for(process, num, options[:port]) port = engine.port_for(process, num, options[:port])
+16 -16
View File
@@ -3,58 +3,58 @@ require "foreman/export"
class Foreman::Export::Runit < Foreman::Export::Base class Foreman::Export::Runit < Foreman::Export::Base
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/ ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
def export(location, options={}) def export(location, options={})
error("Must specify a location") unless location error("Must specify a location") unless location
app = options[:app] || File.basename(engine.directory) app = options[:app] || File.basename(engine.directory)
user = options[:user] || app user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}" log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template] template_root = options[:template]
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency]) concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
run_template = export_template('runit', 'run.erb', template_root) run_template = export_template('runit', 'run.erb', template_root)
log_run_template = export_template('runit', 'log_run.erb', template_root) log_run_template = export_template('runit', 'log_run.erb', template_root)
engine.processes.each do |process| engine.procfile.entries.each do |process|
1.upto(concurrency[process.name]) do |num| 1.upto(concurrency[process.name]) do |num|
process_directory = "#{location}/#{app}-#{process.name}-#{num}" process_directory = "#{location}/#{app}-#{process.name}-#{num}"
process_env_directory = "#{process_directory}/env" process_env_directory = "#{process_directory}/env"
process_log_directory = "#{process_directory}/log" process_log_directory = "#{process_directory}/log"
create_directory process_directory create_directory process_directory
create_directory process_env_directory create_directory process_env_directory
create_directory process_log_directory create_directory process_log_directory
run = ERB.new(run_template).result(binding) run = ERB.new(run_template).result(binding)
write_file "#{process_directory}/run", run write_file "#{process_directory}/run", run
port = engine.port_for(process, num, options[:port]) port = engine.port_for(process, num, options[:port])
environment_variables = {'PORT' => port}. environment_variables = {'PORT' => port}.
merge(engine.environment). merge(engine.environment).
merge(inline_variables(process.command)) merge(inline_variables(process.command))
environment_variables.each_pair do |var, env| environment_variables.each_pair do |var, env|
write_file "#{process_env_directory}/#{var.upcase}", env write_file "#{process_env_directory}/#{var.upcase}", env
end end
log_run = ERB.new(log_run_template).result(binding) log_run = ERB.new(log_run_template).result(binding)
write_file "#{process_log_directory}/run", log_run write_file "#{process_log_directory}/run", log_run
end end
end end
end end
private private
def create_directory(location) def create_directory(location)
say "creating: #{location}" say "creating: #{location}"
FileUtils.mkdir(location) FileUtils.mkdir(location)
end end
def inline_variables(command) def inline_variables(command)
variable_name_regex = variable_name_regex =
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten] Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
end end
end end
+1 -1
View File
@@ -26,7 +26,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
process_template = export_template("upstart", "process.conf.erb", template_root) process_template = export_template("upstart", "process.conf.erb", template_root)
engine.processes.each do |process| engine.procfile.entries.each do |process|
next if (conc = concurrency[process.name]) < 1 next if (conc = concurrency[process.name]) < 1
process_master_template = export_template("upstart", "process_master.conf.erb", template_root) process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
process_master_config = ERB.new(process_master_template).result(binding) process_master_config = ERB.new(process_master_template).result(binding)
+19 -12
View File
@@ -5,24 +5,18 @@ class Foreman::Process
attr_reader :entry attr_reader :entry
attr_reader :num attr_reader :num
attr_reader :pid attr_reader :pid
attr_reader :port
def initialize(entry, num) def initialize(entry, num, port)
@entry = entry @entry = entry
@num = num @num = num
@port = port
end end
def run(pipe, basedir, environment) def run(pipe, basedir, environment)
Dir.chdir(basedir) do Dir.chdir(basedir) do
with_environment(environment) do with_environment(environment.merge("PORT" => port.to_s)) do
io = IO.popen(["/Users/david/Code/foreman/bin/runner", "#{entry.command}"], "w+") run_process entry.command
@pid = io.pid
trap("SIGTERM") { "got sigterm for %d" % @pid }
output pipe, "started with pid %d" % @pid
Thread.new do
until io.eof?
output pipe, io.gets
end
end
end end
end end
end end
@@ -33,11 +27,24 @@ class Foreman::Process
private private
def run_process(command)
io = IO.popen([Foreman.runner, replace_command_env(command)], "w+")
@pid = io.pid
trap("SIGTERM") { "got sigterm for %d" % @pid }
output pipe, "started with pid %d" % @pid
Thread.new do
until io.eof?
output pipe, io.gets
end
end
end
def output(pipe, message) def output(pipe, message)
pipe.puts "%s,%s" % [ name, message ] pipe.puts "%s,%s" % [ name, message ]
end end
def replace_command def replace_command_env(command)
command.gsub(/\$(\w+)/) { |e| ENV[e[1..-1]] }
end end
def with_environment(environment) def with_environment(environment)
+2 -2
View File
@@ -11,9 +11,9 @@ class Foreman::ProcfileEntry
@command = command @command = command
end end
def spawn(num, pipe, basedir, environment) def spawn(num, pipe, basedir, environment, base_port)
(1..num).to_a.map do |n| (1..num).to_a.map do |n|
process = Foreman::Process.new(self, n) process = Foreman::Process.new(self, n, base_port + (n-1))
process.run(pipe, basedir, environment) process.run(pipe, basedir, environment)
process process
end end
+12 -25
View File
@@ -24,8 +24,9 @@ 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.procfile["alpha"]) mock.instance_of(Foreman::Process).run_process("./alpha")
mock(subject).fork(subject.procfile["bravo"]) mock.instance_of(Foreman::Process).run_process("./bravo")
mock(subject).watch_for_output
mock(subject).watch_for_termination mock(subject).watch_for_termination
subject.start subject.start
end end
@@ -33,29 +34,14 @@ describe "Foreman::Engine" do
it "handles concurrency" do it "handles concurrency" do
write_procfile write_procfile
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2") engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
mock(engine).fork_individual(engine.procfile["alpha"], 1, 5000) mock.instance_of(Foreman::Process).run_process("./alpha").twice
mock(engine).fork_individual(engine.procfile["alpha"], 2, 5001) mock.instance_of(Foreman::Process).run_process("./bravo")
mock(engine).fork_individual(engine.procfile["bravo"], 1, 5100) mock(engine).watch_for_output
mock(engine).watch_for_termination mock(engine).watch_for_termination
engine.start engine.start
end end
end end
describe "execute" do
it "runs the processes" do
write_procfile
mock(subject).fork(subject.procfile["alpha"])
mock(subject).watch_for_termination
subject.execute("alpha")
end
it "shows an error running a process that doesnt exist" do
write_procfile
mock(subject).puts("ERROR: no such process: foo")
lambda { subject.execute("foo") }.should raise_error(SystemExit)
end
end
describe "environment" do describe "environment" do
before(:each) do before(:each) do
write_procfile write_procfile
@@ -66,9 +52,10 @@ describe "Foreman::Engine" do
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") } File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env") engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
stub(engine).info stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination mock(engine).watch_for_termination
engine.environment.should == {"FOO"=>"baz"} engine.environment.should == {"FOO"=>"baz"}
engine.execute("alpha") engine.start
end end
it "should read more than one if specified" do it "should read more than one if specified" do
@@ -76,9 +63,10 @@ describe "Foreman::Engine" do
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") } File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2") engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
stub(engine).info stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination mock(engine).watch_for_termination
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" } engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
engine.execute("alpha") engine.start
end end
it "should fail if specified and doesnt exist" do it "should fail if specified and doesnt exist" do
@@ -89,11 +77,10 @@ describe "Foreman::Engine" do
it "should read .env if none specified" do it "should read .env if none specified" do
File.open(".env", "w") { |f| f.puts("FOO=qoo") } File.open(".env", "w") { |f| f.puts("FOO=qoo") }
engine = Foreman::Engine.new("Procfile") engine = Foreman::Engine.new("Procfile")
stub(engine).info mock(engine).spawn_processes
mock(engine).watch_for_termination mock(engine).watch_for_termination
mock(engine).fork_individual(anything, anything, anything)
engine.environment.should == {"FOO"=>"qoo"} engine.environment.should == {"FOO"=>"qoo"}
engine.execute("bravo") engine.start
end end
end end
end end
+1 -2
View File
@@ -13,8 +13,7 @@ describe Foreman::Export::Bluepill do
it "exports to the filesystem" do it "exports to the filesystem" do
bluepill.export("/tmp/init", :concurrency => "alpha=2") bluepill.export("/tmp/init", :concurrency => "alpha=2")
File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill") File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill")
end end
end end
+3 -3
View File
@@ -19,7 +19,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children| process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}" children.stop_command "kill -QUIT {{PID}}"
end end
process.group = "app-alpha" process.group = "app-alpha"
end end
@@ -37,7 +37,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children| process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}" children.stop_command "kill -QUIT {{PID}}"
end end
process.group = "app-alpha" process.group = "app-alpha"
end end
@@ -57,7 +57,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children| process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}" children.stop_command "kill -QUIT {{PID}}"
end end
process.group = "app-bravo" process.group = "app-bravo"
end end