Compare commits

...
19 Commits
Author SHA1 Message Date
David Dollar fd234b8044 0.18.0 2011-06-03 01:32:14 -04:00
David Dollar f2f09554c8 correct shutdown signals
processes are sent SIGTERM followed 3 seconds later by SIGKILL
2011-06-03 01:31:51 -04:00
David Dollar 6c465b4ef1 remove debug print 2011-06-03 01:31:08 -04:00
David Dollar c419f8213b 0.17.0 2011-06-02 12:10:30 -04:00
David Dollar 7f61fb61ea credits 2011-06-02 12:10:21 -04:00
David Dollar 577a5c7c5c make sure tests run on machines other than mine 2011-06-02 12:09:30 -04:00
David Dollar 56940c56d9 Merge pull request #31 from jayzes/master
Loading the shell environment in the exported upstart configuration
2011-06-02 08:51:45 -07:00
Jay Zeschin f308ad886d Change directories when using su - -c to execute a command since you lose the current working directory 2011-06-01 10:38:07 -06:00
Jay Zeschin 55375b9bde Edited data/export/upstart/process.conf.erb via GitHub 2011-05-31 16:30:45 -07:00
David Dollar 85fcccffa8 more readme touchup 2011-05-13 12:10:38 -04:00
David Dollar 58fc18d015 fix up readme 2011-05-13 12:08:41 -04:00
David Dollar f7c9802ef7 0.16.0 2011-05-13 12:05:58 -04:00
David Dollar 1c00d65f29 env is only available in foreground mode for now 2011-05-13 12:05:47 -04:00
David Dollar 9193a675a3 add support for process environments 2011-05-13 12:02:13 -04:00
David Dollar 8f0b14810c Merge pull request #20 from dpiddy/master
Change requires so export/upstart_spec can run on its own
2011-05-13 09:02:01 -07:00
Dan Peterson 124e27ed22 Change requires so export/upstart_spec can be run on its own. 2011-05-13 11:44:53 -03:00
David Dollar f34f161899 0.15.0 2011-05-12 13:05:43 -04:00
David Dollar 191581fe85 kill with TERM, even when INT is received by foreman 2011-05-12 13:05:25 -04:00
David Dollar b98d558bed add blog post to readme 2011-05-12 11:29:56 -04:00
29 changed files with 175 additions and 71 deletions
+1
View File
@@ -0,0 +1 @@
FOO=bar
+1 -1
View File
@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
foreman (0.14.0) foreman (0.18.0)
term-ansicolor (~> 1.0.5) term-ansicolor (~> 1.0.5)
thor (>= 0.13.6) thor (>= 0.13.6)
+21
View File
@@ -1,9 +1,30 @@
# Foreman # Foreman
## Installation
gem install foreman
## Description
http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
## Manual ## Manual
See the [man page](http://ddollar.github.com/foreman) for usage. See the [man page](http://ddollar.github.com/foreman) for usage.
## Authorship
Created by David Dollar
Patches contributed by:
* Adam Wiggins
* clifff
* Dan Peterson
* Jay Zeschin
* Keith Rarick
* Ricardo Chimal, Jr
## License ## License
MIT MIT
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env ruby
exec ARGV.join(" ")
+7 -1
View File
@@ -1,6 +1,12 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
%w( SIGINT SIGTERM SIGHUP ).each do |signal|
trap(signal) do
puts "received #{signal} but i'm ignoring it!"
end
end
while true while true
puts "tick: #{ARGV.inspect}" puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
sleep 1 sleep 1
end end
+1 -2
View File
@@ -2,5 +2,4 @@ start on starting <%= app %>-<%= process.name %>
stop on stopping <%= app %>-<%= process.name %> stop on stopping <%= app %>-<%= process.name %>
respawn respawn
chdir <%= engine.directory %> exec su - <%= user %> -c 'cd <%= engine.directory %>; export 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'
+2 -2
View File
@@ -10,9 +10,9 @@ 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 :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", method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
:banner => '"alpha=5,bar=3"'
def start(process=nil) def start(process=nil)
check_procfile! check_procfile!
+57 -17
View File
@@ -3,6 +3,7 @@ require "foreman/process"
require "foreman/utils" require "foreman/utils"
require "pty" require "pty"
require "tempfile" require "tempfile"
require "timeout"
require "term/ansicolor" require "term/ansicolor"
require "fileutils" require "fileutils"
@@ -50,23 +51,27 @@ class Foreman::Engine
end end
def start(options={}) def start(options={})
environment = read_environment(options[:env])
proctitle "ruby: foreman master" proctitle "ruby: foreman master"
processes_in_order.each do |name, process| processes_in_order.each do |name, process|
fork process, options fork process, options, environment
end end
trap("TERM") { puts "SIGTERM received"; kill_all("TERM") } trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; kill_all("INT") } trap("INT") { puts "SIGINT received"; terminate_gracefully }
watch_for_termination watch_for_termination
end end
def execute(name, options={}) def execute(name, options={})
fork processes[name], options environment = read_environment(options[:env])
trap("TERM") { puts "SIGTERM received"; kill_all("TERM") } fork processes[name], options, environment
trap("INT") { puts "SIGINT received"; kill_all("INT") }
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully }
watch_for_termination watch_for_termination
end end
@@ -79,15 +84,17 @@ class Foreman::Engine
private ###################################################################### private ######################################################################
def fork(process, options={}) def fork(process, options={}, environment={})
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency]) concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
1.upto(concurrency[process.name]) do |num| 1.upto(concurrency[process.name]) do |num|
fork_individual(process, num, port_for(process, num, options[:port])) fork_individual(process, num, port_for(process, num, options[:port]), environment)
end end
end end
def fork_individual(process, num, port) def fork_individual(process, num, port, environment)
environment.each { |k,v| ENV[k] = v }
ENV["PORT"] = port.to_s ENV["PORT"] = port.to_s
ENV["PS"] = "#{process.name}.#{num}" ENV["PS"] = "#{process.name}.#{num}"
@@ -101,12 +108,12 @@ private ######################################################################
def run(process) def run(process)
proctitle "ruby: foreman #{process.name}" proctitle "ruby: foreman #{process.name}"
trap("SIGINT", "IGNORE")
begin begin
Dir.chdir directory do Dir.chdir directory do
command = process.command PTY.spawn(runner, process.command) do |stdin, stdout, pid|
trap("SIGTERM") { Process.kill("SIGTERM", pid) }
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
until stdin.eof? until stdin.eof?
info stdin.gets, process info stdin.gets, process
end end
@@ -120,11 +127,9 @@ private ######################################################################
end end
end end
def kill_all(signal="TERM") def kill_all(signal="SIGTERM")
info "terminating"
running_processes.each do |pid, process| running_processes.each do |pid, process|
info "killing #{process.name} in pid #{pid}" Process.kill(signal, pid) rescue Errno::ESRCH
Process.kill(signal, pid)
end end
end end
@@ -136,6 +141,11 @@ private ######################################################################
puts puts
end end
def error(message)
puts "ERROR: #{message}"
exit 1
end
def longest_process_name def longest_process_name
@longest_process_name ||= begin @longest_process_name ||= begin
longest = processes.keys.map { |name| name.length }.sort.last longest = processes.keys.map { |name| name.length }.sort.last
@@ -168,8 +178,9 @@ private ######################################################################
pid, status = Process.wait2 pid, status = Process.wait2
process = running_processes.delete(pid) process = running_processes.delete(pid)
info "process terminated", process info "process terminated", process
terminate_gracefully
kill_all kill_all
Process.waitall rescue Errno::ECHILD
end end
def running_processes def running_processes
@@ -190,4 +201,33 @@ private ######################################################################
puts "!!! e.g. web: thin start" puts "!!! e.g. web: thin start"
end end
def read_environment(filename)
error "No such file: #{filename}" if filename && !File.exists?(filename)
filename ||= ".env"
environment = {}
if File.exists?(filename)
File.read(filename).split("\n").each do |line|
if line =~ /\A([A-Za-z_]+)=(.*)\z/
environment[$1] = $2
end
end
end
environment
end
def runner
File.expand_path("../../../bin/foreman-runner", __FILE__)
end
def terminate_gracefully
info "sending SIGTERM to all processes"
kill_all "SIGTERM"
Timeout.timeout(3) { Process.waitall }
rescue Timeout::Error
info "sending SIGKILL to all processes"
kill_all "SIGKILL"
end
end end
+1
View File
@@ -4,5 +4,6 @@ module Foreman::Export
class Exception < ::Exception; end class Exception < ::Exception; end
end end
require "foreman/export/base"
require "foreman/export/inittab" require "foreman/export/inittab"
require "foreman/export/upstart" require "foreman/export/upstart"
+1 -1
View File
@@ -1,4 +1,4 @@
require "foreman/export/base" require "foreman/export"
class Foreman::Export::Inittab < Foreman::Export::Base class Foreman::Export::Inittab < Foreman::Export::Base
+1 -1
View File
@@ -1,5 +1,5 @@
require "erb" require "erb"
require "foreman/export/base" require "foreman/export"
class Foreman::Export::Upstart < Foreman::Export::Base class Foreman::Export::Upstart < Foreman::Export::Base
+1 -1
View File
@@ -1,5 +1,5 @@
module Foreman module Foreman
VERSION = "0.14.0" VERSION = "0.18.0"
end end
+5 -1
View File
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
.TH "FOREMAN" "1" "May 2011" "Foreman 0.13.0" "Foreman Manual" .TH "FOREMAN" "1" "May 2011" "Foreman 0.15.0" "Foreman Manual"
. .
.SH "NAME" .SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications \fBforeman\fR \- manage Procfile\-based applications
@@ -68,6 +68,10 @@ Specify the user the application should be run as\. Defaults to the app name
These options control all modes of foreman\'s operation\. These options control all modes of foreman\'s operation\.
. .
.TP .TP
\fB\-e\fR, \fB\-\-env\fR
Specify a file containing the environment that should be set up for each child process\. The file should be key/value pairs separated by \fB=\fR, with one key/value pair per line\.
.
.TP
\fB\-f\fR, \fB\-\-procfile\fR \fB\-f\fR, \fB\-\-procfile\fR
Specify an alternate location for the application\'s Procfile\. This file\'s containing directory will be assumed to be the root directory of the application\. Specify an alternate location for the application\'s Procfile\. This file\'s containing directory will be assumed to be the root directory of the application\.
. .
+5
View File
@@ -66,6 +66,11 @@ The following options control how the application is run:
These options control all modes of foreman's operation. These options control all modes of foreman's operation.
* `-e`, `--env`:
Specify a file containing the environment that should be set up for each
child process. The file should be key/value pairs separated by `=`, with
one key/value pair per line.
* `-f`, `--procfile`: * `-f`, `--procfile`:
Specify an alternate location for the application's Procfile. This file's Specify an alternate location for the application's Procfile. This file's
containing directory will be assumed to be the root directory of the containing directory will be assumed to be the root directory of the
+31 -6
View File
@@ -37,17 +37,17 @@ 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 it "handles concurrency" do
write_procfile write_procfile
mock(subject).fork_individual(subject.processes["alpha"], 1, 5000) mock(subject).fork_individual(subject.processes["alpha"], 1, 5000, {})
mock(subject).fork_individual(subject.processes["alpha"], 2, 5001) mock(subject).fork_individual(subject.processes["alpha"], 2, 5001, {})
mock(subject).fork_individual(subject.processes["bravo"], 1, 5100) mock(subject).fork_individual(subject.processes["bravo"], 1, 5100, {})
mock(subject).watch_for_termination mock(subject).watch_for_termination
subject.start(:concurrency => "alpha=2") subject.start(:concurrency => "alpha=2")
end end
@@ -56,9 +56,34 @@ describe "Foreman::Engine" do
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
end end
describe "environment" do
before(:each) do
write_procfile
stub(Process).fork
stub(subject).info
mock(subject).watch_for_termination
end
it "should read if specified" do
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
subject.execute("alpha", :env => "/tmp/env")
end
it "should fail if specified and doesnt exist" do
mock(subject).error("No such file: /tmp/env")
subject.execute("alpha", :env => "/tmp/env")
end
it "should read .env if none specified" do
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
mock(subject).fork_individual(anything, anything, anything, { "FOO" => "qoo" })
subject.execute("bravo")
end
end
end end
+10 -7
View File
@@ -1,8 +1,11 @@
require "spec_helper" require "spec_helper"
require "foreman/engine"
require "foreman/export/upstart" require "foreman/export/upstart"
require "tmpdir"
describe Foreman::Export::Upstart do describe Foreman::Export::Upstart do
let(:engine) { Foreman::Engine.new(write_procfile) } let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:engine) { Foreman::Engine.new(procfile) }
let(:upstart) { Foreman::Export::Upstart.new(engine) } let(:upstart) { Foreman::Export::Upstart.new(engine) }
before(:each) { load_export_templates_into_fakefs("upstart") } before(:each) { load_export_templates_into_fakefs("upstart") }
@@ -11,11 +14,11 @@ describe Foreman::Export::Upstart do
it "exports to the filesystem" do it "exports to the filesystem" do
upstart.export("/tmp/init") upstart.export("/tmp/init")
File.read("/tmp/init/foreman.conf").should == example_export_file("upstart/foreman.conf") File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
File.read("/tmp/init/foreman-alpha.conf").should == example_export_file("upstart/foreman-alpha.conf") File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
File.read("/tmp/init/foreman-alpha-1.conf").should == example_export_file("upstart/foreman-alpha-1.conf") File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
File.read("/tmp/init/foreman-alpha-2.conf").should == example_export_file("upstart/foreman-alpha-2.conf") File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("upstart/app-alpha-2.conf")
File.read("/tmp/init/foreman-bravo.conf").should == example_export_file("upstart/foreman.bravo.conf") File.read("/tmp/init/app-bravo.conf").should == example_export_file("upstart/app-bravo.conf")
File.read("/tmp/init/foreman-bravo-1.conf").should == example_export_file("upstart/foreman-bravo-1.conf") File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf")
end end
end end
@@ -0,0 +1,5 @@
start on starting app-alpha
stop on stopping app-alpha
respawn
exec su - app -c 'cd /tmp/app; export PORT=5000; ./alpha >> /var/log/app/alpha-1.log 2>&1'
@@ -0,0 +1,5 @@
start on starting app-alpha
stop on stopping app-alpha
respawn
exec su - app -c 'cd /tmp/app; export PORT=5001; ./alpha >> /var/log/app/alpha-2.log 2>&1'
@@ -0,0 +1,2 @@
start on starting app
stop on stopping app
@@ -0,0 +1,5 @@
start on starting app-bravo
stop on stopping app-bravo
respawn
exec su - app -c 'cd /tmp/app; export PORT=5100; ./bravo >> /var/log/app/bravo-1.log 2>&1'
@@ -0,0 +1,2 @@
start on starting app
stop on stopping app
+8
View File
@@ -0,0 +1,8 @@
pre-start script
bash << "EOF"
mkdir -p /var/log/app
chown -R app /var/log/app
EOF
end script
@@ -1,6 +0,0 @@
start on starting foreman-alpha
stop on stopping foreman-alpha
respawn
chdir /Users/david/Code/foreman
exec su foreman -c 'export PORT=5000; ./alpha >> /var/log/foreman/alpha-1.log 2>&1'
@@ -1,6 +0,0 @@
start on starting foreman-alpha
stop on stopping foreman-alpha
respawn
chdir /Users/david/Code/foreman
exec su foreman -c 'export PORT=5001; ./alpha >> /var/log/foreman/alpha-2.log 2>&1'
@@ -1,2 +0,0 @@
start on starting foreman
stop on stopping foreman
@@ -1,6 +0,0 @@
start on starting foreman-bravo
stop on stopping foreman-bravo
respawn
chdir /Users/david/Code/foreman
exec su foreman -c 'export PORT=5100; ./bravo >> /var/log/foreman/bravo-1.log 2>&1'
@@ -1,2 +0,0 @@
start on starting foreman
stop on stopping foreman
@@ -1,8 +0,0 @@
pre-start script
bash << "EOF"
mkdir -p /var/log/foreman
chown -R foreman /var/log/foreman
EOF
end script
+1 -1
View File
@@ -3,7 +3,7 @@ require "rspec"
require "fakefs/safe" require "fakefs/safe"
require "fakefs/spec_helpers" require "fakefs/spec_helpers"
$:.unshift "lib" $:.unshift File.expand_path("../../lib", __FILE__)
def mock_error(subject, message) def mock_error(subject, message)
mock_exit do mock_exit do