This commit is contained in:
David Dollar
2011-12-08 17:53:13 -08:00
parent c9411cd2b1
commit 5436b68cf1
13 changed files with 72 additions and 79 deletions

View File

@@ -1,5 +1,2 @@
#!/bin/sh
echo "command[$*][$1]"
exec $1 2>&1

View File

@@ -3,7 +3,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
app.uid = "<%= user %>"
app.gid = "<%= user %>"
<% engine.processes.each do |process| %>
<% engine.procfile.entries.each do |process| %>
<% 1.upto(concurrency[process.name]) do |num| %>
<% port = engine.port_for(process, num, options[:port]) %>
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|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "<%= app %>-<%= process.name %>"
end
<% end %>

View File

@@ -9,5 +9,10 @@ module Foreman
require 'foreman/engine'
Foreman::Engine.load_env!(env_file)
end
def self.runner
File.expand_path("../../bin/runner", __FILE__)
end
end

View File

@@ -8,20 +8,15 @@ class Foreman::CLI < Thor
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 :port, :type => :numeric, :aliases => "-p"
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
def start(process=nil)
def start
check_procfile!
if process
engine.execute(process)
else
engine.start
end
engine.start
end
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"
def check
error "no processes defined" unless engine.processes.length > 0
display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})"
error "no processes defined" unless engine.procfile.entries.length > 0
display "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
end
private ######################################################################

View File

@@ -57,16 +57,19 @@ private ######################################################################
procfile.entries.each do |entry|
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
readers[process] = reader
end
end
end
def base_port
options[:port] || 5000
end
def kill_all(signal="SIGTERM")
running_processes.each do |pid, process|
p [:killing, pid]
Process.kill(signal, pid) rescue Errno::ESRCH
end
end

View File

@@ -12,7 +12,7 @@ class Foreman::Export::Inittab < Foreman::Export::Base
inittab = []
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|
id = app.slice(0, 2).upcase + sprintf("%02d", index)
port = engine.port_for(process, num, options[:port])

View File

@@ -3,58 +3,58 @@ require "foreman/export"
class Foreman::Export::Runit < Foreman::Export::Base
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
def export(location, options={})
error("Must specify a location") unless location
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template]
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
run_template = export_template('runit', '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|
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
process_env_directory = "#{process_directory}/env"
process_log_directory = "#{process_directory}/log"
create_directory process_directory
create_directory process_env_directory
create_directory process_log_directory
run = ERB.new(run_template).result(binding)
write_file "#{process_directory}/run", run
port = engine.port_for(process, num, options[:port])
environment_variables = {'PORT' => port}.
merge(engine.environment).
merge(inline_variables(process.command))
environment_variables.each_pair do |var, env|
write_file "#{process_env_directory}/#{var.upcase}", env
end
log_run = ERB.new(log_run_template).result(binding)
write_file "#{process_log_directory}/run", log_run
end
end
end
private
def create_directory(location)
say "creating: #{location}"
FileUtils.mkdir(location)
end
def inline_variables(command)
variable_name_regex =
variable_name_regex =
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
end
end
end

View File

@@ -26,7 +26,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
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
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
process_master_config = ERB.new(process_master_template).result(binding)

View File

@@ -5,24 +5,18 @@ class Foreman::Process
attr_reader :entry
attr_reader :num
attr_reader :pid
attr_reader :port
def initialize(entry, num)
def initialize(entry, num, port)
@entry = entry
@num = num
@port = port
end
def run(pipe, basedir, environment)
Dir.chdir(basedir) do
with_environment(environment) do
io = IO.popen(["/Users/david/Code/foreman/bin/runner", "#{entry.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
with_environment(environment.merge("PORT" => port.to_s)) do
run_process entry.command
end
end
end
@@ -33,11 +27,24 @@ class Foreman::Process
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)
pipe.puts "%s,%s" % [ name, message ]
end
def replace_command
def replace_command_env(command)
command.gsub(/\$(\w+)/) { |e| ENV[e[1..-1]] }
end
def with_environment(environment)

View File

@@ -11,9 +11,9 @@ class Foreman::ProcfileEntry
@command = command
end
def spawn(num, pipe, basedir, environment)
def spawn(num, pipe, basedir, environment, base_port)
(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
end

View File

@@ -24,8 +24,9 @@ describe "Foreman::Engine" do
describe "start" do
it "forks the processes" do
write_procfile
mock(subject).fork(subject.procfile["alpha"])
mock(subject).fork(subject.procfile["bravo"])
mock.instance_of(Foreman::Process).run_process("./alpha")
mock.instance_of(Foreman::Process).run_process("./bravo")
mock(subject).watch_for_output
mock(subject).watch_for_termination
subject.start
end
@@ -33,29 +34,14 @@ describe "Foreman::Engine" do
it "handles concurrency" do
write_procfile
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
mock(engine).fork_individual(engine.procfile["alpha"], 1, 5000)
mock(engine).fork_individual(engine.procfile["alpha"], 2, 5001)
mock(engine).fork_individual(engine.procfile["bravo"], 1, 5100)
mock.instance_of(Foreman::Process).run_process("./alpha").twice
mock.instance_of(Foreman::Process).run_process("./bravo")
mock(engine).watch_for_output
mock(engine).watch_for_termination
engine.start
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
before(:each) do
write_procfile
@@ -66,9 +52,10 @@ describe "Foreman::Engine" do
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == {"FOO"=>"baz"}
engine.execute("alpha")
engine.start
end
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") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
engine.execute("alpha")
engine.start
end
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
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
engine = Foreman::Engine.new("Procfile")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
mock(engine).fork_individual(anything, anything, anything)
engine.environment.should == {"FOO"=>"qoo"}
engine.execute("bravo")
engine.start
end
end
end

View File

@@ -13,8 +13,7 @@ describe Foreman::Export::Bluepill do
it "exports to the filesystem" do
bluepill.export("/tmp/init", :concurrency => "alpha=2")
File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill")
end
end
end

View File

@@ -19,7 +19,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-alpha"
end
@@ -37,7 +37,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-alpha"
end
@@ -57,7 +57,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-bravo"
end