Compare commits

..

9 Commits

Author SHA1 Message Date
David Dollar
be73e8500f 0.24.0 2011-10-04 10:43:33 -04:00
David Dollar
d26ca669a1 define procfile regex, refactoring 2011-10-04 10:38:13 -04:00
David Dollar
a3e758ab6c 0.23.1 2011-10-04 09:56:39 -04:00
David Dollar
5dc232a7b1 Merge pull request #79 from fdr/runner-dead-code
Eliminate dead code
2011-10-04 05:14:50 -07:00
Dan Farina
4191cb7b9c Eliminate dead code
An oversight; this code should probably have been pruned in
20e598abcc.

Signed-off-by: Dan Farina <drfarina@acm.org>
2011-10-04 00:19:04 -07:00
David Dollar
90d4dffb82 tweak docs 2011-09-16 18:46:10 -04:00
David Dollar
823f307abc doc updates 2011-09-16 18:42:57 -04:00
David Dollar
ed44a11e21 0.23.0 2011-09-16 18:25:02 -04:00
David Dollar
5719f4fc72 allow multiple environment files to be specified 2011-09-16 18:24:38 -04:00
11 changed files with 112 additions and 119 deletions

View File

@@ -1,7 +1,7 @@
PATH
remote: .
specs:
foreman (0.22.0)
foreman (0.24.0)
term-ansicolor (~> 1.0.5)
thor (>= 0.13.6)

View File

@@ -1,2 +1,2 @@
ticker: ruby ./ticker $PORT
error : ruby ./error
error: ruby ./error

View File

@@ -3,7 +3,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
app.uid = "<%= user %>"
app.gid = "<%= user %>"
<% engine.processes.values.each do |process| %>
<% engine.processes.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|
@@ -24,4 +24,4 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
end
<% end %>
<% end %>
end
end

View File

@@ -53,9 +53,8 @@ class Foreman::CLI < Thor
desc "check", "Validate your application's Procfile"
def check
processes = engine.processes_in_order.map { |p| p.first }
error "no processes defined" unless processes.length > 0
display "valid procfile detected (#{processes.join(', ')})"
error "no processes defined" unless engine.processes.length > 0
display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})"
end
private ######################################################################

View File

@@ -1,5 +1,6 @@
require "foreman"
require "foreman/process"
require "foreman/procfile"
require "foreman/utils"
require "pty"
require "tempfile"
@@ -19,45 +20,17 @@ class Foreman::Engine
COLORS = [ cyan, yellow, green, magenta, red ]
def initialize(procfile, options={})
@procfile = read_procfile(procfile)
@procfile = Foreman::Procfile.new(procfile)
@directory = File.expand_path(File.dirname(procfile))
@options = options
@environment = read_environment(options[:env])
end
def processes
@processes ||= begin
@order = []
procfile.split("\n").inject({}) do |hash, line|
next hash if line.strip == ""
name, command = line.split(/\s*:\s+/, 2)
unless command
warn_deprecated_procfile!
name, command = line.split(/ +/, 2)
end
process = Foreman::Process.new(name, command)
process.color = next_color
@order << process.name
hash.update(process.name => process)
end
end
end
def process_order
processes
@order.uniq
end
def processes_in_order
process_order.map do |name|
[name, processes[name]]
end
@environment = read_environment_files(options[:env])
end
def start
proctitle "ruby: foreman master"
processes_in_order.each do |name, process|
processes.each do |process|
process.color = next_color
fork process
end
@@ -68,8 +41,7 @@ class Foreman::Engine
end
def execute(name)
fork processes[name]
fork procfile[name]
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully }
@@ -77,9 +49,13 @@ class Foreman::Engine
watch_for_termination
end
def processes
procfile.processes
end
def port_for(process, num, base_port=nil)
base_port ||= 5000
offset = processes_in_order.map { |p| p.first }.index(process.name) * 100
offset = procfile.process_names.index(process.name) * 100
base_port.to_i + offset + num - 1
end
@@ -134,6 +110,24 @@ private ######################################################################
end
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
def watch_for_termination
pid, status = Process.wait2
process = running_processes.delete(pid)
info "process terminated", process
terminate_gracefully
kill_all
rescue Errno::ECHILD
end
def info(message, process=nil)
print process.color if process
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
@@ -149,7 +143,7 @@ private ######################################################################
def longest_process_name
@longest_process_name ||= begin
longest = processes.keys.map { |name| name.length }.sort.last
longest = procfile.process_names.map { |name| name.length }.sort.last
longest = 6 if longest < 6 # system
longest
end
@@ -160,30 +154,10 @@ private ######################################################################
name.ljust(longest_process_name + 3) # add 3 for process number padding
end
def print_info
info "currently running processes:"
running_processes.each do |pid, process|
info "pid #{pid}", process
end
end
def proctitle(title)
$0 = title
end
def read_procfile(procfile)
File.read(procfile)
end
def watch_for_termination
pid, status = Process.wait2
process = running_processes.delete(pid)
info "process terminated", process
terminate_gracefully
kill_all
rescue Errno::ECHILD
end
def running_processes
@running_processes ||= {}
end
@@ -194,41 +168,27 @@ private ######################################################################
@current_color >= COLORS.length ? "" : COLORS[@current_color]
end
def warn_deprecated_procfile!
return if @already_warned_deprecated
@already_warned_deprecated = true
puts "!!! This format of Procfile is deprecated, and will not work starting in v0.12"
puts "!!! Use a colon to separate the process name from the command"
puts "!!! e.g. web: thin start"
end
def read_environment(filename)
error "No such file: #{filename}" if filename && !File.exists?(filename)
filename ||= ".env"
def read_environment_files(filenames)
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
(filenames || "").split(",").map(&:strip).each do |filename|
error "No such file: #{filename}" unless File.exists?(filename)
environment.merge!(read_environment(filename))
end
environment.merge!(read_environment(".env")) unless filenames
environment
end
def runner
File.expand_path("../../../bin/foreman-runner", __FILE__)
end
def read_environment(filename)
return {} unless File.exists?(filename)
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"
File.read(filename).split("\n").inject({}) do |hash, line|
if line =~ /\A([A-Za-z_]+)=(.*)\z/
hash[$1] = $2
end
hash
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.values.each do |process|
engine.processes.each do |process|
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
process_master_config = ERB.new(process_master_template).result(binding)
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config

37
lib/foreman/procfile.rb Normal file
View File

@@ -0,0 +1,37 @@
require "foreman"
# A valid Procfile entry is captured by this regex.
# All other lines are ignored.
#
# /^([A-Za-z0-9_]+):\s*(.+)$/
#
# $1 = name
# $2 = command
#
class Foreman::Procfile
attr_reader :processes
def initialize(filename)
@processes = parse_procfile(filename)
end
def process_names
processes.map(&:name)
end
def [](name)
processes.detect { |process| process.name == name }
end
private
def parse_procfile(filename)
File.read(filename).split("\n").map do |line|
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
Foreman::Process.new($1, $2)
end
end.compact
end
end

View File

@@ -1,5 +1,5 @@
module Foreman
VERSION = "0.22.0"
VERSION = "0.24.0"
end

View File

@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "FOREMAN" "1" "September 2011" "Foreman 0.21.0" "Foreman Manual"
.TH "FOREMAN" "1" "September 2011" "Foreman 0.23.0" "Foreman Manual"
.
.SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications
@@ -69,7 +69,7 @@ These options control all modes of foreman\'s operation\.
.
.TP
\fB\-e\fR, \fB\-\-env\fR
Specify an alternate environment file\.
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
.
.TP
\fB\-f\fR, \fB\-\-procfile\fR
@@ -165,7 +165,7 @@ If a \fB\.foreman\fR file exists in the current directory, default options will
.
.nf
concurrency: alpha=0
concurrency: alpha=0,bravo=1
port: 15000
.
.fi

View File

@@ -67,7 +67,8 @@ The following options control how the application is run:
These options control all modes of foreman's operation.
* `-e`, `--env`:
Specify an alternate environment file.
Specify an alternate environment file. You can specify more than one
file by using: `--env file1,file2`.
* `-f`, `--procfile`:
Specify an alternate location for the application's Procfile. This file's
@@ -131,7 +132,7 @@ If a `.foreman` file exists in the current directory, default options will
be read from it. This file should be in YAML format with the long option
name as keys. Example:
concurrency: alpha=0
concurrency: alpha=0,bravo=1
port: 15000
## EXAMPLES

View File

@@ -15,21 +15,8 @@ describe "Foreman::Engine" do
before { write_procfile }
it "reads the processes" do
subject.processes["alpha"].command.should == "./alpha"
subject.processes["bravo"].command.should == "./bravo"
end
end
describe "with a deprecated Procfile" do
before do
File.open("Procfile", "w") do |file|
file.puts "name command"
end
end
it "should print a deprecation warning" do
mock(subject).warn_deprecated_procfile!
subject.processes.length.should == 1
subject.procfile["alpha"].command.should == "./alpha"
subject.procfile["bravo"].command.should == "./bravo"
end
end
end
@@ -37,8 +24,8 @@ describe "Foreman::Engine" do
describe "start" do
it "forks the processes" do
write_procfile
mock(subject).fork(subject.processes["alpha"])
mock(subject).fork(subject.processes["bravo"])
mock(subject).fork(subject.procfile["alpha"])
mock(subject).fork(subject.procfile["bravo"])
mock(subject).watch_for_termination
subject.start
end
@@ -46,9 +33,9 @@ describe "Foreman::Engine" do
it "handles concurrency" do
write_procfile
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
mock(engine).fork_individual(engine.processes["alpha"], 1, 5000)
mock(engine).fork_individual(engine.processes["alpha"], 2, 5001)
mock(engine).fork_individual(engine.processes["bravo"], 1, 5100)
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(engine).watch_for_termination
engine.start
end
@@ -57,14 +44,13 @@ describe "Foreman::Engine" do
describe "execute" do
it "runs the processes" do
write_procfile
mock(subject).fork(subject.processes["alpha"])
mock(subject).fork(subject.procfile["alpha"])
mock(subject).watch_for_termination
subject.execute("alpha")
end
end
describe "environment" do
before(:each) do
write_procfile
stub(Process).fork
@@ -79,6 +65,16 @@ describe "Foreman::Engine" do
engine.execute("alpha")
end
it "should read more than one if specified" do
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
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).watch_for_termination
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
engine.execute("alpha")
end
it "should fail if specified and doesnt exist" do
mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")