Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be73e8500f | ||
|
|
d26ca669a1 | ||
|
|
a3e758ab6c | ||
|
|
5dc232a7b1 | ||
|
|
4191cb7b9c | ||
|
|
90d4dffb82 | ||
|
|
823f307abc | ||
|
|
ed44a11e21 | ||
|
|
5719f4fc72 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
foreman (0.22.0)
|
foreman (0.24.0)
|
||||||
term-ansicolor (~> 1.0.5)
|
term-ansicolor (~> 1.0.5)
|
||||||
thor (>= 0.13.6)
|
thor (>= 0.13.6)
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
ticker: ruby ./ticker $PORT
|
ticker: ruby ./ticker $PORT
|
||||||
error : ruby ./error
|
error: ruby ./error
|
||||||
|
|||||||
@@ -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.values.each do |process| %>
|
<% engine.processes.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|
|
||||||
@@ -24,4 +24,4 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|||||||
end
|
end
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
end
|
end
|
||||||
|
|||||||
+2
-3
@@ -53,9 +53,8 @@ class Foreman::CLI < Thor
|
|||||||
desc "check", "Validate your application's Procfile"
|
desc "check", "Validate your application's Procfile"
|
||||||
|
|
||||||
def check
|
def check
|
||||||
processes = engine.processes_in_order.map { |p| p.first }
|
error "no processes defined" unless engine.processes.length > 0
|
||||||
error "no processes defined" unless processes.length > 0
|
display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})"
|
||||||
display "valid procfile detected (#{processes.join(', ')})"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private ######################################################################
|
private ######################################################################
|
||||||
|
|||||||
+43
-83
@@ -1,5 +1,6 @@
|
|||||||
require "foreman"
|
require "foreman"
|
||||||
require "foreman/process"
|
require "foreman/process"
|
||||||
|
require "foreman/procfile"
|
||||||
require "foreman/utils"
|
require "foreman/utils"
|
||||||
require "pty"
|
require "pty"
|
||||||
require "tempfile"
|
require "tempfile"
|
||||||
@@ -19,45 +20,17 @@ class Foreman::Engine
|
|||||||
COLORS = [ cyan, yellow, green, magenta, red ]
|
COLORS = [ cyan, yellow, green, magenta, red ]
|
||||||
|
|
||||||
def initialize(procfile, options={})
|
def initialize(procfile, options={})
|
||||||
@procfile = read_procfile(procfile)
|
@procfile = Foreman::Procfile.new(procfile)
|
||||||
@directory = File.expand_path(File.dirname(procfile))
|
@directory = File.expand_path(File.dirname(procfile))
|
||||||
@options = options
|
@options = options
|
||||||
@environment = read_environment(options[:env])
|
@environment = read_environment_files(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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
proctitle "ruby: foreman master"
|
proctitle "ruby: foreman master"
|
||||||
|
|
||||||
processes_in_order.each do |name, process|
|
processes.each do |process|
|
||||||
|
process.color = next_color
|
||||||
fork process
|
fork process
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -68,8 +41,7 @@ class Foreman::Engine
|
|||||||
end
|
end
|
||||||
|
|
||||||
def execute(name)
|
def execute(name)
|
||||||
|
fork procfile[name]
|
||||||
fork processes[name]
|
|
||||||
|
|
||||||
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
||||||
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
||||||
@@ -77,9 +49,13 @@ class Foreman::Engine
|
|||||||
watch_for_termination
|
watch_for_termination
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def processes
|
||||||
|
procfile.processes
|
||||||
|
end
|
||||||
|
|
||||||
def port_for(process, num, base_port=nil)
|
def port_for(process, num, base_port=nil)
|
||||||
base_port ||= 5000
|
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
|
base_port.to_i + offset + num - 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -134,6 +110,24 @@ private ######################################################################
|
|||||||
end
|
end
|
||||||
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)
|
def info(message, process=nil)
|
||||||
print process.color if process
|
print process.color if process
|
||||||
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
|
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
|
||||||
@@ -149,7 +143,7 @@ private ######################################################################
|
|||||||
|
|
||||||
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 = procfile.process_names.map { |name| name.length }.sort.last
|
||||||
longest = 6 if longest < 6 # system
|
longest = 6 if longest < 6 # system
|
||||||
longest
|
longest
|
||||||
end
|
end
|
||||||
@@ -160,30 +154,10 @@ private ######################################################################
|
|||||||
name.ljust(longest_process_name + 3) # add 3 for process number padding
|
name.ljust(longest_process_name + 3) # add 3 for process number padding
|
||||||
end
|
end
|
||||||
|
|
||||||
def print_info
|
|
||||||
info "currently running processes:"
|
|
||||||
running_processes.each do |pid, process|
|
|
||||||
info "pid #{pid}", process
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def proctitle(title)
|
def proctitle(title)
|
||||||
$0 = title
|
$0 = title
|
||||||
end
|
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
|
def running_processes
|
||||||
@running_processes ||= {}
|
@running_processes ||= {}
|
||||||
end
|
end
|
||||||
@@ -194,41 +168,27 @@ private ######################################################################
|
|||||||
@current_color >= COLORS.length ? "" : COLORS[@current_color]
|
@current_color >= COLORS.length ? "" : COLORS[@current_color]
|
||||||
end
|
end
|
||||||
|
|
||||||
def warn_deprecated_procfile!
|
def read_environment_files(filenames)
|
||||||
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"
|
|
||||||
environment = {}
|
environment = {}
|
||||||
|
|
||||||
if File.exists?(filename)
|
(filenames || "").split(",").map(&:strip).each do |filename|
|
||||||
File.read(filename).split("\n").each do |line|
|
error "No such file: #{filename}" unless File.exists?(filename)
|
||||||
if line =~ /\A([A-Za-z_]+)=(.*)\z/
|
environment.merge!(read_environment(filename))
|
||||||
environment[$1] = $2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
environment.merge!(read_environment(".env")) unless filenames
|
||||||
environment
|
environment
|
||||||
end
|
end
|
||||||
|
|
||||||
def runner
|
def read_environment(filename)
|
||||||
File.expand_path("../../../bin/foreman-runner", __FILE__)
|
return {} unless File.exists?(filename)
|
||||||
end
|
|
||||||
|
|
||||||
def terminate_gracefully
|
File.read(filename).split("\n").inject({}) do |hash, line|
|
||||||
info "sending SIGTERM to all processes"
|
if line =~ /\A([A-Za-z_]+)=(.*)\z/
|
||||||
kill_all "SIGTERM"
|
hash[$1] = $2
|
||||||
Timeout.timeout(3) { Process.waitall }
|
end
|
||||||
rescue Timeout::Error
|
hash
|
||||||
info "sending SIGKILL to all processes"
|
end
|
||||||
kill_all "SIGKILL"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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.values.each do |process|
|
engine.processes.each do |process|
|
||||||
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)
|
||||||
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
module Foreman
|
module Foreman
|
||||||
|
|
||||||
VERSION = "0.22.0"
|
VERSION = "0.24.0"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
+3
-3
@@ -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" "September 2011" "Foreman 0.21.0" "Foreman Manual"
|
.TH "FOREMAN" "1" "September 2011" "Foreman 0.23.0" "Foreman Manual"
|
||||||
.
|
.
|
||||||
.SH "NAME"
|
.SH "NAME"
|
||||||
\fBforeman\fR \- manage Procfile\-based applications
|
\fBforeman\fR \- manage Procfile\-based applications
|
||||||
@@ -69,7 +69,7 @@ These options control all modes of foreman\'s operation\.
|
|||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
\fB\-e\fR, \fB\-\-env\fR
|
\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
|
.TP
|
||||||
\fB\-f\fR, \fB\-\-procfile\fR
|
\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
|
.nf
|
||||||
|
|
||||||
concurrency: alpha=0
|
concurrency: alpha=0,bravo=1
|
||||||
port: 15000
|
port: 15000
|
||||||
.
|
.
|
||||||
.fi
|
.fi
|
||||||
|
|||||||
+3
-2
@@ -67,7 +67,8 @@ 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`:
|
* `-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`:
|
* `-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
|
||||||
@@ -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
|
be read from it. This file should be in YAML format with the long option
|
||||||
name as keys. Example:
|
name as keys. Example:
|
||||||
|
|
||||||
concurrency: alpha=0
|
concurrency: alpha=0,bravo=1
|
||||||
port: 15000
|
port: 15000
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|||||||
+18
-22
@@ -15,21 +15,8 @@ describe "Foreman::Engine" do
|
|||||||
before { write_procfile }
|
before { write_procfile }
|
||||||
|
|
||||||
it "reads the processes" do
|
it "reads the processes" do
|
||||||
subject.processes["alpha"].command.should == "./alpha"
|
subject.procfile["alpha"].command.should == "./alpha"
|
||||||
subject.processes["bravo"].command.should == "./bravo"
|
subject.procfile["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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -37,8 +24,8 @@ 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.procfile["alpha"])
|
||||||
mock(subject).fork(subject.processes["bravo"])
|
mock(subject).fork(subject.procfile["bravo"])
|
||||||
mock(subject).watch_for_termination
|
mock(subject).watch_for_termination
|
||||||
subject.start
|
subject.start
|
||||||
end
|
end
|
||||||
@@ -46,9 +33,9 @@ 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.processes["alpha"], 1, 5000)
|
mock(engine).fork_individual(engine.procfile["alpha"], 1, 5000)
|
||||||
mock(engine).fork_individual(engine.processes["alpha"], 2, 5001)
|
mock(engine).fork_individual(engine.procfile["alpha"], 2, 5001)
|
||||||
mock(engine).fork_individual(engine.processes["bravo"], 1, 5100)
|
mock(engine).fork_individual(engine.procfile["bravo"], 1, 5100)
|
||||||
mock(engine).watch_for_termination
|
mock(engine).watch_for_termination
|
||||||
engine.start
|
engine.start
|
||||||
end
|
end
|
||||||
@@ -57,14 +44,13 @@ 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.procfile["alpha"])
|
||||||
mock(subject).watch_for_termination
|
mock(subject).watch_for_termination
|
||||||
subject.execute("alpha")
|
subject.execute("alpha")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "environment" do
|
describe "environment" do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
write_procfile
|
write_procfile
|
||||||
stub(Process).fork
|
stub(Process).fork
|
||||||
@@ -79,6 +65,16 @@ describe "Foreman::Engine" do
|
|||||||
engine.execute("alpha")
|
engine.execute("alpha")
|
||||||
end
|
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
|
it "should fail if specified and doesnt exist" do
|
||||||
mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
|
mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
|
||||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
||||||
|
|||||||
Reference in New Issue
Block a user