Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a258b8dc3 | ||
|
|
eeeef65c88 | ||
|
|
d67a2f4e11 | ||
|
|
ce5c8b4c04 | ||
|
|
9d859dae92 | ||
|
|
04884366b3 | ||
|
|
8c78d1e1ee | ||
|
|
35a5f972fe | ||
|
|
868bc44a4e | ||
|
|
644956db29 | ||
|
|
bd07ed809d | ||
|
|
26bb8995c9 | ||
|
|
7005860c3c | ||
|
|
90356ca41d | ||
|
|
2ce3a15bb7 | ||
|
|
f960277ae8 | ||
|
|
4f5402af4a | ||
|
|
a27f964881 | ||
|
|
cf008385b4 | ||
|
|
f633a579d6 | ||
|
|
ea90bf3615 | ||
|
|
c65c71b1c0 | ||
|
|
6f10f4f014 | ||
|
|
be6d1b805c | ||
|
|
58e936a7e2 | ||
|
|
d4f29d6909 | ||
|
|
571163795f | ||
|
|
845ee9ef38 | ||
|
|
d5d774c9c2 | ||
|
|
b77952ff7f | ||
|
|
3495fa2fea | ||
|
|
4741dceeb0 | ||
|
|
dc1cb08d27 | ||
|
|
b51e8046ce | ||
|
|
922eb7438e | ||
|
|
6811a8d96a | ||
|
|
0f7eec061d | ||
|
|
27e53eb916 | ||
|
|
b123e6b3c5 | ||
|
|
c2000484bb | ||
|
|
75f0ce4b9c | ||
|
|
d508d44fd2 | ||
|
|
35cc880d40 | ||
|
|
6434db289b | ||
|
|
1a118ee92b | ||
|
|
7bd176063c | ||
|
|
fff958f7b6 | ||
|
|
7888ee8c52 | ||
|
|
a6197c183e | ||
|
|
41a0620126 | ||
|
|
86654d7918 | ||
|
|
9929165d17 | ||
|
|
2b8d575aab | ||
|
|
56f8603a5d | ||
|
|
3f48d7c541 | ||
|
|
675ad2630d | ||
|
|
9004bf67e1 | ||
|
|
19c425d200 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
coverage
|
||||
example/log/*
|
||||
man/*.?
|
||||
man/*.html
|
||||
man/*.markdown
|
||||
pkg
|
||||
|
||||
96
README.markdown
Normal file
96
README.markdown
Normal file
@@ -0,0 +1,96 @@
|
||||
foreman(1) -- manage Procfile-based applications
|
||||
================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`foreman start [process]`<br>
|
||||
`foreman export <var>format</var> [location]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**Foreman** is a manager for Procfile-based applications. Its aim is to
|
||||
abstract away the details of the Procfile format, and allow you to either run
|
||||
your application directly or export it to some other process management
|
||||
format.
|
||||
|
||||
## RUNNING
|
||||
|
||||
`foreman start` is used to run your application directly from the command line.
|
||||
|
||||
If no additional parameters are passed, foreman will run one instance of each
|
||||
type of process defined in your Procfile.
|
||||
|
||||
If a parameter is passed, foreman will run one instance of the specified
|
||||
application type.
|
||||
|
||||
The following options control how the application is run:
|
||||
|
||||
* `-s`, `--screen`:
|
||||
Run the application as a series of screen windows rather than interleaved
|
||||
in stdout.
|
||||
|
||||
## EXPORTING
|
||||
|
||||
`foreman export` is used to export your application to another process
|
||||
management format.
|
||||
|
||||
An location to export can be passed as an argument. This argument may be
|
||||
either required or optional depending on the export format.
|
||||
|
||||
The following options control how the application is run:
|
||||
|
||||
* `-a`, `--app`:
|
||||
Use this name rather than the application's root directory name as the
|
||||
name of the application when exporting.
|
||||
|
||||
* `-c`, `--concurrency`:
|
||||
Specify the number of each process type to run. The value passed in
|
||||
should be in the format `process=num,process=num`
|
||||
|
||||
## OPTIONS
|
||||
|
||||
These options control all modes of foreman's operation.
|
||||
|
||||
* `-p`, `--procfile`
|
||||
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.
|
||||
|
||||
## PROCFILE
|
||||
|
||||
A Procfile should contain both a name for the process and the command used
|
||||
to run it.
|
||||
|
||||
web bundle exec thin start
|
||||
job bundle exec rake jobs:work
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Start one instance of each process type, interleave the output on stdout:
|
||||
|
||||
$ foreman start
|
||||
|
||||
Export the application in upstart format:
|
||||
|
||||
$ foreman export upstart /etc/init
|
||||
|
||||
Run one process type from the application defined in a specific Procfile:
|
||||
|
||||
$ foreman start alpha -p ~/app/Procfile
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
Foreman is Copyright (C) 2010 David Dollar <http://daviddollar.org>
|
||||
|
||||
|
||||
[SYNOPSIS]: #SYNOPSIS "SYNOPSIS"
|
||||
[DESCRIPTION]: #DESCRIPTION "DESCRIPTION"
|
||||
[RUNNING]: #RUNNING "RUNNING"
|
||||
[EXPORTING]: #EXPORTING "EXPORTING"
|
||||
[OPTIONS]: #OPTIONS "OPTIONS"
|
||||
[PROCFILE]: #PROCFILE "PROCFILE"
|
||||
[EXAMPLES]: #EXAMPLES "EXAMPLES"
|
||||
[COPYRIGHT]: #COPYRIGHT "COPYRIGHT"
|
||||
|
||||
|
||||
[foreman(1)]: foreman.1.html
|
||||
77
README.rdoc
77
README.rdoc
@@ -1,77 +0,0 @@
|
||||
= Foreman
|
||||
|
||||
=== Procfile
|
||||
|
||||
alpha ./bin/alpha
|
||||
bravo ./bin/bravo some args
|
||||
charlie ./bin/charlie -n 5
|
||||
|
||||
== Development mode
|
||||
|
||||
=== Running
|
||||
|
||||
$ foreman start
|
||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [alpha] started with pid 4393
|
||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [bravo] started with pid 4394
|
||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [charlie] started with pid 4395
|
||||
|
||||
=== Standardized Logging
|
||||
|
||||
log/alpha.log
|
||||
log/bravo.log
|
||||
log/charlie.log
|
||||
|
||||
== Upstart
|
||||
|
||||
=== Export to upstart scripts
|
||||
|
||||
$ foreman export sampleapp
|
||||
|
||||
$ initctl list | grep sampleapp
|
||||
sampleapp start/running
|
||||
sampleapp-alpha (1) start/running, process 4204
|
||||
sampleapp-bravo (1) start/running, process 4589
|
||||
sampleapp-charlie (1) start/running, process 4597
|
||||
|
||||
=== Change process concurrency levels
|
||||
|
||||
$ foreman scale sampleapp alpha 4
|
||||
sampleapp-alpha (2) start/running, process 4164
|
||||
sampleapp-alpha (3) start/running, process 4166
|
||||
sampleapp-alpha (4) start/running, process 4168
|
||||
|
||||
$ initctl list | grep sampleapp
|
||||
sampleapp start/running
|
||||
sampleapp-alpha (4) start/running, process 4168
|
||||
sampleapp-alpha (3) start/running, process 4166
|
||||
sampleapp-alpha (2) start/running, process 4164
|
||||
sampleapp-alpha (1) start/running, process 4204
|
||||
sampleapp-bravo (1) start/running, process 4589
|
||||
sampleapp-charlie (1) start/running, process 4597
|
||||
|
||||
$ foreman scale sampleapp alpha 1
|
||||
sampleapp-alpha stop/waiting
|
||||
sampleapp-alpha stop/waiting
|
||||
sampleapp-alpha stop/waiting
|
||||
|
||||
=== Good Upstart citizen
|
||||
|
||||
All Upstart commands work as expected
|
||||
|
||||
$ start sampleapp
|
||||
$ stop sampleapp
|
||||
$ restart sampleapp
|
||||
|
||||
=== Standardized Logging
|
||||
|
||||
/var/log/sampleapp/alpha.log
|
||||
/var/log/sampleapp/bravo.log
|
||||
/var/log/sampleapp/charlie.log
|
||||
|
||||
== License
|
||||
|
||||
MIT
|
||||
|
||||
== Copyright
|
||||
|
||||
(c) 2010 David Dollar
|
||||
30
Rakefile
30
Rakefile
@@ -1,11 +1,12 @@
|
||||
require "rubygems"
|
||||
require "rake"
|
||||
require "rspec"
|
||||
require "rspec/core/rake_task"
|
||||
|
||||
$:.unshift File.expand_path("../lib", __FILE__)
|
||||
require "foreman"
|
||||
|
||||
task :default => :spec
|
||||
task :release => :man
|
||||
|
||||
desc "Run all specs"
|
||||
Rspec::Core::RakeTask.new(:spec) do |t|
|
||||
@@ -23,6 +24,29 @@ Rspec::Core::RakeTask.new("rcov:build") do |t|
|
||||
t.rcov_opts = [ "--exclude", Gem.default_dir , "--exclude", "spec" ]
|
||||
end
|
||||
|
||||
desc 'Build the manual'
|
||||
task :man do
|
||||
ENV['RONN_MANUAL'] = "Foreman Manual"
|
||||
ENV['RONN_ORGANIZATION'] = "Foreman #{Foreman::VERSION}"
|
||||
sh "ronn -w -s toc -r5 --markdown man/*.ronn"
|
||||
sh "cp man/foreman.1.markdown README.markdown"
|
||||
sh "git add README.markdown"
|
||||
sh "git commit -m 'update readme' || echo 'nothing to commit'"
|
||||
end
|
||||
|
||||
task :pages => :man do
|
||||
sh %{
|
||||
cp man/foreman.1.html /tmp/foreman.1.html
|
||||
git checkout gh-pages
|
||||
rm ./index.html
|
||||
cp /tmp/foreman.1.html ./index.html
|
||||
git add -u index.html
|
||||
git commit -m "rebuilding man page"
|
||||
git push origin -f gh-pages
|
||||
git checkout master
|
||||
}
|
||||
end
|
||||
|
||||
######################################################
|
||||
|
||||
begin
|
||||
@@ -40,7 +64,7 @@ begin
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.has_rdoc = false
|
||||
|
||||
s.files = %w(Rakefile README.md) + Dir["{bin,lib,spec}/**/*"]
|
||||
s.files = %w(Rakefile README.md) + Dir["{bin,export,lib,spec}/**/*"]
|
||||
s.require_path = "lib"
|
||||
|
||||
# #s.bindir = "bin"
|
||||
@@ -53,8 +77,10 @@ begin
|
||||
s.add_development_dependency 'rr', '~> 0.10.11'
|
||||
s.add_development_dependency 'rspec', '~> 2.0.0'
|
||||
|
||||
s.add_dependency 'term-ansicolor', '~> 1.0.5'
|
||||
s.add_dependency 'thor', '~> 0.13.6'
|
||||
end
|
||||
Jeweler::GemcutterTasks.new
|
||||
rescue LoadError
|
||||
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
||||
end
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
neverdie ./never_die
|
||||
diealot ./die_alot
|
||||
error ./error
|
||||
ticker ./ticker
|
||||
error ./error
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
puts "sleeping for 2s then dying"
|
||||
sleep 2
|
||||
exit 0
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
puts "will error in 10s"
|
||||
sleep 10
|
||||
sleep 5
|
||||
raise "Dying"
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
|
||||
while true
|
||||
puts "tick"
|
||||
$stdout.flush
|
||||
sleep 5
|
||||
sleep 1
|
||||
end
|
||||
13
export/upstart/master.conf.erb
Normal file
13
export/upstart/master.conf.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p /var/log/<%= app %>
|
||||
|
||||
<% engine.processes.keys.sort.each do |process| %>
|
||||
<% 1.upto(concurrency[process]).each do |num| %>
|
||||
start <%=app%>-<%=process%>-<%=num%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
EOF
|
||||
|
||||
end script
|
||||
5
export/upstart/process.conf.erb
Normal file
5
export/upstart/process.conf.erb
Normal file
@@ -0,0 +1,5 @@
|
||||
stop on stopping <%= app %>
|
||||
respawn
|
||||
|
||||
chdir <%= engine.directory %>
|
||||
exec <%= process.command %> >> /var/log/<%=app%>/<%=process.name%>-<%=num%>.log 2>&1
|
||||
@@ -5,26 +5,29 @@
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{foreman}
|
||||
s.version = "0.1.0"
|
||||
s.version = "0.4.4"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["David Dollar"]
|
||||
s.date = %q{2010-05-21}
|
||||
s.date = %q{2010-06-23}
|
||||
s.default_executable = %q{foreman}
|
||||
s.description = %q{Process manager for applications with multiple components}
|
||||
s.email = %q{ddollar@gmail.com}
|
||||
s.executables = ["foreman"]
|
||||
s.extra_rdoc_files = [
|
||||
"README.rdoc"
|
||||
"README.markdown"
|
||||
]
|
||||
s.files = [
|
||||
"Rakefile",
|
||||
"bin/foreman",
|
||||
"export/upstart/master.conf.erb",
|
||||
"export/upstart/process.conf.erb",
|
||||
"lib/foreman.rb",
|
||||
"lib/foreman/cli.rb",
|
||||
"lib/foreman/configuration.rb",
|
||||
"lib/foreman/engine.rb",
|
||||
"lib/foreman/export.rb",
|
||||
"lib/foreman/export/base.rb",
|
||||
"lib/foreman/export/upstart.rb",
|
||||
"lib/foreman/process.rb",
|
||||
"spec/foreman/cli_spec.rb",
|
||||
@@ -63,6 +66,7 @@ Gem::Specification.new do |s|
|
||||
s.add_development_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||
s.add_development_dependency(%q<rr>, ["~> 0.10.11"])
|
||||
s.add_development_dependency(%q<rspec>, ["~> 2.0.0"])
|
||||
s.add_runtime_dependency(%q<term-ansicolor>, ["~> 1.0.5"])
|
||||
s.add_runtime_dependency(%q<thor>, ["~> 0.13.6"])
|
||||
else
|
||||
s.add_dependency(%q<fakefs>, ["~> 0.2.1"])
|
||||
@@ -70,6 +74,7 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
|
||||
s.add_dependency(%q<term-ansicolor>, ["~> 1.0.5"])
|
||||
s.add_dependency(%q<thor>, ["~> 0.13.6"])
|
||||
end
|
||||
else
|
||||
@@ -78,6 +83,7 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
|
||||
s.add_dependency(%q<term-ansicolor>, ["~> 1.0.5"])
|
||||
s.add_dependency(%q<thor>, ["~> 0.13.6"])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module Foreman
|
||||
|
||||
VERSION = "0.1.0"
|
||||
VERSION = "0.4.4"
|
||||
|
||||
class AppDoesNotExist < Exception; end
|
||||
|
||||
|
||||
@@ -6,32 +6,58 @@ require "thor"
|
||||
|
||||
class Foreman::CLI < Thor
|
||||
|
||||
desc "start [PROCFILE]", "Run the app described in PROCFILE"
|
||||
class_option :procfile, :type => :string, :aliases => "-p", :desc => "Default: ./Procfile"
|
||||
|
||||
def start(procfile="Procfile")
|
||||
error "#{procfile} does not exist." unless procfile_exists?(procfile)
|
||||
Foreman::Engine.new(procfile).start
|
||||
desc "start [PROCESS]", "Start the application, or a specific process"
|
||||
|
||||
method_option :screen, :type => :boolean, :aliases => "-s"
|
||||
|
||||
def start(process=nil)
|
||||
check_procfile!
|
||||
|
||||
if process
|
||||
engine.execute(process)
|
||||
elsif options[:screen]
|
||||
engine.screen
|
||||
else
|
||||
engine.start
|
||||
end
|
||||
end
|
||||
|
||||
desc "export APP [PROCFILE] [FORMAT]", "Export the app described in PROCFILE as APP to another FORMAT"
|
||||
desc "export FORMAT LOCATION", "Export the application to another process management format"
|
||||
|
||||
def export(app, procfile="Procfile", format="upstart")
|
||||
error "#{procfile} does not exist." unless procfile_exists?(procfile)
|
||||
method_option :app, :type => :string, :aliases => "-a"
|
||||
method_option :concurrency, :type => :string, :aliases => "-c",
|
||||
:banner => '"alpha=5,bar=3"'
|
||||
|
||||
def export(format, location=nil)
|
||||
check_procfile!
|
||||
|
||||
formatter = case format
|
||||
when "upstart" then Foreman::Export::Upstart
|
||||
else error "Unknown export format: #{format}."
|
||||
end
|
||||
|
||||
formatter.new(Foreman::Engine.new(procfile)).export(app)
|
||||
formatter.new(engine).export(location,
|
||||
:name => options[:app],
|
||||
:concurrency => options[:concurrency]
|
||||
)
|
||||
rescue Foreman::Export::Exception => ex
|
||||
error ex.message
|
||||
end
|
||||
|
||||
desc "scale APP PROCESS AMOUNT", "Change the concurrency of a given process type"
|
||||
private ######################################################################
|
||||
|
||||
def scale(app, process, amount)
|
||||
config = Foreman::Configuration.new(app)
|
||||
error "No such process: #{process}." unless config.processes[process]
|
||||
config.scale(process, amount)
|
||||
def check_procfile!
|
||||
error("Procfile does not exist.") unless File.exist?(procfile)
|
||||
end
|
||||
|
||||
def engine
|
||||
@engine ||= Foreman::Engine.new(procfile)
|
||||
end
|
||||
|
||||
def procfile
|
||||
options[:procfile] || "./Procfile"
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
require "foreman"
|
||||
require "foreman/process"
|
||||
require "pty"
|
||||
require "tempfile"
|
||||
require "term/ansicolor"
|
||||
|
||||
class Foreman::Engine
|
||||
|
||||
attr_reader :procfile
|
||||
attr_reader :directory
|
||||
|
||||
extend Term::ANSIColor
|
||||
|
||||
COLORS = [ cyan, yellow, green, magenta, on_blue ]
|
||||
|
||||
def initialize(procfile)
|
||||
@procfile = read_procfile(procfile)
|
||||
@directory = File.expand_path(File.dirname(procfile))
|
||||
@@ -16,6 +23,7 @@ class Foreman::Engine
|
||||
procfile.split("\n").inject({}) do |hash, line|
|
||||
next if line.strip == ""
|
||||
process = Foreman::Process.new(*line.split(" ", 2))
|
||||
process.color = next_color
|
||||
hash.update(process.name => process)
|
||||
end
|
||||
end
|
||||
@@ -31,37 +39,84 @@ class Foreman::Engine
|
||||
trap("TERM") { kill_and_exit("TERM") }
|
||||
trap("INT") { kill_and_exit("INT") }
|
||||
|
||||
run_loop
|
||||
watch_for_termination
|
||||
end
|
||||
|
||||
def screen
|
||||
tempfile = Tempfile.new("foreman")
|
||||
tempfile.puts "sessionname foreman"
|
||||
processes.each do |name, process|
|
||||
tempfile.puts "screen -t #{name} #{process.command}"
|
||||
end
|
||||
tempfile.close
|
||||
|
||||
system "screen -c #{tempfile.path}"
|
||||
|
||||
tempfile.delete
|
||||
end
|
||||
|
||||
def execute(name)
|
||||
run(processes[name], false)
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def fork(process)
|
||||
pid = Process.fork do
|
||||
proctitle "ruby: foreman #{process.name}"
|
||||
|
||||
Dir.chdir directory do
|
||||
FileUtils.mkdir_p "log"
|
||||
system "#{process.command} >>log/#{process.name}.log 2>&1"
|
||||
exit $?.exitstatus || 255
|
||||
end
|
||||
run(process)
|
||||
end
|
||||
|
||||
info "started with pid #{pid}", process
|
||||
running_processes[pid] = process
|
||||
end
|
||||
|
||||
def run(process, log_to_file=true)
|
||||
proctitle "ruby: foreman #{process.name}"
|
||||
|
||||
Dir.chdir directory do
|
||||
FileUtils.mkdir_p "log"
|
||||
command = process.command
|
||||
|
||||
begin
|
||||
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
|
||||
until stdin.eof?
|
||||
info stdin.gets, process
|
||||
end
|
||||
end
|
||||
rescue PTY::ChildExited, Interrupt
|
||||
info "process exiting", process
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def kill_and_exit(signal="TERM")
|
||||
info "termination requested"
|
||||
info "terminating"
|
||||
running_processes.each do |pid, process|
|
||||
info "killing pid #{pid}", process
|
||||
info "killing #{process.name} in pid #{pid}"
|
||||
Process.kill(signal, pid)
|
||||
end
|
||||
exit 0
|
||||
end
|
||||
|
||||
def info(message, process=nil)
|
||||
puts "[foreman] [#{Time.now.utc}] [#{process ? process.name : "system"}] #{message}"
|
||||
print process.color if process
|
||||
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
|
||||
print Term::ANSIColor.reset
|
||||
print message.chomp
|
||||
puts
|
||||
end
|
||||
|
||||
def longest_process_name
|
||||
@longest_process_name ||= begin
|
||||
longest = processes.keys.map { |name| name.length }.sort.last
|
||||
longest = 6 if longest < 6 # system
|
||||
longest
|
||||
end
|
||||
end
|
||||
|
||||
def pad_process_name(process)
|
||||
name = process ? process.name : "system"
|
||||
name.ljust(longest_process_name)
|
||||
end
|
||||
|
||||
def print_info
|
||||
@@ -79,17 +134,21 @@ private ######################################################################
|
||||
File.read(procfile)
|
||||
end
|
||||
|
||||
def run_loop
|
||||
while true
|
||||
pid, status = Process.wait2
|
||||
process = running_processes.delete(pid)
|
||||
info "exited with code #{status}", process
|
||||
fork process
|
||||
end
|
||||
def watch_for_termination
|
||||
pid, status = Process.wait2
|
||||
process = running_processes.delete(pid)
|
||||
info "process terminated", process
|
||||
kill_and_exit
|
||||
end
|
||||
|
||||
def running_processes
|
||||
@running_processes ||= {}
|
||||
end
|
||||
|
||||
def next_color
|
||||
@current_color ||= -1
|
||||
@current_color += 1
|
||||
@current_color >= COLORS.length ? "" : COLORS[@current_color]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require "foreman"
|
||||
|
||||
module Foreman::Export
|
||||
class Exception < ::Exception; end
|
||||
end
|
||||
|
||||
require "foreman/export/upstart"
|
||||
|
||||
48
lib/foreman/export/base.rb
Normal file
48
lib/foreman/export/base.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require "foreman/configuration"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Base
|
||||
|
||||
attr_reader :engine
|
||||
|
||||
def initialize(engine)
|
||||
@engine = engine
|
||||
end
|
||||
|
||||
def export
|
||||
raise "export method must be overridden"
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def error(message)
|
||||
raise Foreman::Export::Exception.new(message)
|
||||
end
|
||||
|
||||
def say(message)
|
||||
puts "[foreman export] %s" % message
|
||||
end
|
||||
|
||||
def export_template(name)
|
||||
File.read(File.expand_path("../../../../export/#{name}", __FILE__))
|
||||
end
|
||||
|
||||
def parse_concurrency(concurrency)
|
||||
@concurrency ||= begin
|
||||
pairs = concurrency.to_s.gsub(/\s/, "").split(",")
|
||||
pairs.inject(Hash.new(1)) do |hash, pair|
|
||||
process, amount = pair.split("=")
|
||||
hash.update(process => amount.to_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_file(filename, contents)
|
||||
say "writing: #{filename}"
|
||||
|
||||
File.open(filename, "w") do |file|
|
||||
file.puts contents
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,66 +1,49 @@
|
||||
require "erb"
|
||||
require "foreman/configuration"
|
||||
require "foreman/export"
|
||||
require "foreman/export/base"
|
||||
|
||||
class Foreman::Export::Upstart
|
||||
class Foreman::Export::Upstart < Foreman::Export::Base
|
||||
|
||||
attr_reader :engine
|
||||
def export(location, options={})
|
||||
error("Must specify a location") unless location
|
||||
|
||||
def initialize(engine)
|
||||
@engine = engine
|
||||
end
|
||||
FileUtils.mkdir_p location
|
||||
|
||||
def export(app)
|
||||
FileUtils.mkdir_p "/etc/foreman"
|
||||
FileUtils.mkdir_p "/etc/init"
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
|
||||
config = Foreman::Configuration.new(app)
|
||||
Dir["#{location}/#{app}*.conf"].each do |file|
|
||||
say "cleaning up: #{file}"
|
||||
FileUtils.rm(file)
|
||||
end
|
||||
|
||||
write_file "/etc/init/#{app}.conf", <<-UPSTART_MASTER
|
||||
pre-start script
|
||||
concurrency = parse_concurrency(options[:concurrency])
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p /var/log/#{app}
|
||||
master_template = export_template("upstart/master.conf.erb")
|
||||
master_config = ERB.new(master_template).result(binding)
|
||||
write_file "#{location}/#{app}.conf", master_config
|
||||
|
||||
if [ -f /etc/foreman/#{app}.conf ]; then
|
||||
source /etc/foreman/#{app}.conf
|
||||
fi
|
||||
process_template = export_template("upstart/process.conf.erb")
|
||||
|
||||
engine.processes.values.each do |process|
|
||||
1.upto(concurrency[process.name]) do |num|
|
||||
process_config = ERB.new(process_template).result(binding)
|
||||
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
|
||||
end
|
||||
end
|
||||
|
||||
for process in $( echo "$#{app}_processes" ); do
|
||||
process_count_config="#{app}_$process"
|
||||
process_count=${!process_count_config}
|
||||
|
||||
for ((i=1; i<=${process_count:=1}; i+=1)); do
|
||||
start #{app}-$process NUM=$i
|
||||
done
|
||||
done
|
||||
EOF
|
||||
|
||||
end script
|
||||
return
|
||||
write_file "#{location}/#{app}.conf", <<-UPSTART_MASTER
|
||||
UPSTART_MASTER
|
||||
|
||||
engine.processes.values.each do |process|
|
||||
write_file "/etc/init/#{app}-#{process.name}.conf", <<-UPSTART_CHILD
|
||||
instance $NUM
|
||||
stop on stopping #{app}
|
||||
respawn
|
||||
|
||||
chdir #{engine.directory}
|
||||
exec #{process.command} >>/var/log/#{app}/#{process.name}.log 2>&1
|
||||
engine.processes.each do |process|
|
||||
write_file process_conf, <<-UPSTART_CHILD
|
||||
UPSTART_CHILD
|
||||
end
|
||||
|
||||
engine.processes.each do |name, process|
|
||||
config.scale(name, 1)
|
||||
config.processes[name] ||= 1
|
||||
end
|
||||
config.write
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def write_file(filename, contents)
|
||||
File.open(filename, "w") do |file|
|
||||
file.puts contents
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ class Foreman::Process
|
||||
|
||||
attr_reader :name
|
||||
attr_reader :command
|
||||
attr_accessor :color
|
||||
|
||||
def initialize(name, command)
|
||||
@name = name
|
||||
|
||||
83
man/foreman.1.ronn
Normal file
83
man/foreman.1.ronn
Normal file
@@ -0,0 +1,83 @@
|
||||
foreman(1) -- manage Procfile-based applications
|
||||
================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`foreman start [process]`<br>
|
||||
`foreman export <format> [location]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**Foreman** is a manager for Procfile-based applications. Its aim is to
|
||||
abstract away the details of the Procfile format, and allow you to either run
|
||||
your application directly or export it to some other process management
|
||||
format.
|
||||
|
||||
## RUNNING
|
||||
|
||||
`foreman start` is used to run your application directly from the command line.
|
||||
|
||||
If no additional parameters are passed, foreman will run one instance of each
|
||||
type of process defined in your Procfile.
|
||||
|
||||
If a parameter is passed, foreman will run one instance of the specified
|
||||
application type.
|
||||
|
||||
The following options control how the application is run:
|
||||
|
||||
* `-s`, `--screen`:
|
||||
Run the application as a series of screen windows rather than interleaved
|
||||
in stdout.
|
||||
|
||||
## EXPORTING
|
||||
|
||||
`foreman export` is used to export your application to another process
|
||||
management format.
|
||||
|
||||
An location to export can be passed as an argument. This argument may be
|
||||
either required or optional depending on the export format.
|
||||
|
||||
The following options control how the application is run:
|
||||
|
||||
* `-a`, `--app`:
|
||||
Use this name rather than the application's root directory name as the
|
||||
name of the application when exporting.
|
||||
|
||||
* `-c`, `--concurrency`:
|
||||
Specify the number of each process type to run. The value passed in
|
||||
should be in the format `process=num,process=num`
|
||||
|
||||
## OPTIONS
|
||||
|
||||
These options control all modes of foreman's operation.
|
||||
|
||||
* `-p`, `--procfile`
|
||||
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.
|
||||
|
||||
## PROCFILE
|
||||
|
||||
A Procfile should contain both a name for the process and the command used
|
||||
to run it.
|
||||
|
||||
web bundle exec thin start
|
||||
job bundle exec rake jobs:work
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Start one instance of each process type, interleave the output on stdout:
|
||||
|
||||
$ foreman start
|
||||
|
||||
Export the application in upstart format:
|
||||
|
||||
$ foreman export upstart /etc/init
|
||||
|
||||
Run one process type from the application defined in a specific Procfile:
|
||||
|
||||
$ foreman start alpha -p ~/app/Procfile
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
Foreman is Copyright (C) 2010 David Dollar <http://daviddollar.org>
|
||||
@@ -5,9 +5,7 @@ describe "Foreman::CLI" do
|
||||
subject { Foreman::CLI.new }
|
||||
|
||||
describe "start" do
|
||||
#let(:engine) { stub_engine }
|
||||
|
||||
describe "with a non-existent Procifile" do
|
||||
describe "with a non-existent Procfile" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Procfile does not exist.") do
|
||||
dont_allow.instance_of(Foreman::Engine).start
|
||||
@@ -28,7 +26,7 @@ describe "Foreman::CLI" do
|
||||
end
|
||||
|
||||
describe "export" do
|
||||
describe "with a non-existent Procifile" do
|
||||
describe "with a non-existent Procfile" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Procfile does not exist.") do
|
||||
dont_allow.instance_of(Foreman::Engine).export
|
||||
@@ -43,7 +41,7 @@ describe "Foreman::CLI" do
|
||||
describe "with an invalid formatter" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Unknown export format: invalidformatter.") do
|
||||
subject.export("testapp", "Procfile", "invalidformatter")
|
||||
subject.export("invalidformatter")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -53,33 +51,11 @@ describe "Foreman::CLI" do
|
||||
|
||||
it "runs successfully" do
|
||||
dont_allow(subject).error
|
||||
subject.export("testapp")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "scale" do
|
||||
describe "without an existing configuration" do
|
||||
it "displays an error" do
|
||||
mock_error(subject, "No such process: alpha.") do
|
||||
subject.scale("testapp", "alpha", "2")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "with an existing configuration" do
|
||||
before(:each) { write_foreman_config("testapp") }
|
||||
|
||||
it "scales a process that exists" do
|
||||
mock.instance_of(Foreman::Configuration).scale("alpha", "2")
|
||||
subject.scale("testapp", "alpha", "2")
|
||||
end
|
||||
|
||||
it "errors if a process that does not exist is specified" do
|
||||
mock_error(subject, "No such process: invalidprocess.") do
|
||||
dont_allow.instance_of(Foreman::Configuration).scale
|
||||
subject.scale("testapp", "invalidprocess", "2")
|
||||
mock.instance_of(Foreman::Export::Upstart).export("/tmp/foo", {
|
||||
:concurrency => nil,
|
||||
:name => nil
|
||||
})
|
||||
subject.export("upstart", "/tmp/foo")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,8 +25,16 @@ describe "Foreman::Engine" do
|
||||
write_procfile
|
||||
mock(subject).fork(subject.processes["alpha"])
|
||||
mock(subject).fork(subject.processes["bravo"])
|
||||
mock(subject).run_loop
|
||||
mock(subject).watch_for_termination
|
||||
subject.start
|
||||
end
|
||||
end
|
||||
|
||||
describe "execute" do
|
||||
it "runs the processes" do
|
||||
write_procfile
|
||||
mock(subject).run(subject.processes["alpha"], false)
|
||||
subject.execute("alpha")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
require "fakefs/spec_helpers"
|
||||
require "rubygems"
|
||||
require "rspec"
|
||||
require "fakefs/safe"
|
||||
require "fakefs/spec_helpers"
|
||||
|
||||
$:.unshift "lib"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user