Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75f0ce4b9c | ||
|
|
d508d44fd2 | ||
|
|
35cc880d40 | ||
|
|
6434db289b | ||
|
|
1a118ee92b | ||
|
|
7bd176063c | ||
|
|
fff958f7b6 | ||
|
|
7888ee8c52 | ||
|
|
a6197c183e | ||
|
|
41a0620126 | ||
|
|
86654d7918 | ||
|
|
9929165d17 | ||
|
|
2b8d575aab | ||
|
|
56f8603a5d | ||
|
|
3f48d7c541 | ||
|
|
675ad2630d | ||
|
|
9004bf67e1 | ||
|
|
19c425d200 |
+17
-9
@@ -1,6 +1,6 @@
|
|||||||
= Foreman
|
= Foreman
|
||||||
|
|
||||||
=== Procfile
|
== Procfile
|
||||||
|
|
||||||
alpha ./bin/alpha
|
alpha ./bin/alpha
|
||||||
bravo ./bin/bravo some args
|
bravo ./bin/bravo some args
|
||||||
@@ -10,18 +10,26 @@
|
|||||||
|
|
||||||
=== Running
|
=== Running
|
||||||
|
|
||||||
|
Log files will be output to standard out, colorized to aid in visual separation.
|
||||||
|
|
||||||
$ foreman start
|
$ foreman start
|
||||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [alpha] started with pid 4393
|
[01:27:08] [alpha] started with pid 4393
|
||||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [bravo] started with pid 4394
|
[01:27:08] [bravo] started with pid 4394
|
||||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [charlie] started with pid 4395
|
[01:27:08] [charlie] started with pid 4395
|
||||||
|
[01:27:08] [bravo] initializing...
|
||||||
|
[01:27:08] [bravo] complete
|
||||||
|
|
||||||
=== Standardized Logging
|
=== Using Screen
|
||||||
|
|
||||||
log/alpha.log
|
Launch the processes in a screen session in indivudal windows
|
||||||
log/bravo.log
|
|
||||||
log/charlie.log
|
|
||||||
|
|
||||||
== Upstart
|
$ foreman screen
|
||||||
|
|
||||||
|
== Single Process Execution
|
||||||
|
|
||||||
|
$ foreman execute alpha
|
||||||
|
|
||||||
|
== Exporting to Upstart
|
||||||
|
|
||||||
=== Export to upstart scripts
|
=== Export to upstart scripts
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
require "rubygems"
|
|
||||||
require "rake"
|
require "rake"
|
||||||
|
require "rspec"
|
||||||
require "rspec/core/rake_task"
|
require "rspec/core/rake_task"
|
||||||
|
|
||||||
$:.unshift File.expand_path("../lib", __FILE__)
|
$:.unshift File.expand_path("../lib", __FILE__)
|
||||||
@@ -53,8 +53,10 @@ begin
|
|||||||
s.add_development_dependency 'rr', '~> 0.10.11'
|
s.add_development_dependency 'rr', '~> 0.10.11'
|
||||||
s.add_development_dependency 'rspec', '~> 2.0.0'
|
s.add_development_dependency 'rspec', '~> 2.0.0'
|
||||||
|
|
||||||
|
s.add_dependency 'term-ansicolor', '~> 1.0.5'
|
||||||
s.add_dependency 'thor', '~> 0.13.6'
|
s.add_dependency 'thor', '~> 0.13.6'
|
||||||
end
|
end
|
||||||
|
Jeweler::GemcutterTasks.new
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
||||||
end
|
end
|
||||||
|
|||||||
+2
-3
@@ -1,3 +1,2 @@
|
|||||||
neverdie ./never_die
|
ticker ./ticker
|
||||||
diealot ./die_alot
|
error ./error
|
||||||
error ./error
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
puts "sleeping for 2s then dying"
|
|
||||||
sleep 2
|
|
||||||
exit 0
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
puts "will error in 10s"
|
puts "will error in 10s"
|
||||||
sleep 10
|
sleep 5
|
||||||
raise "Dying"
|
raise "Dying"
|
||||||
|
|||||||
@@ -2,6 +2,5 @@
|
|||||||
|
|
||||||
while true
|
while true
|
||||||
puts "tick"
|
puts "tick"
|
||||||
$stdout.flush
|
sleep 1
|
||||||
sleep 5
|
|
||||||
end
|
end
|
||||||
+5
-2
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = %q{foreman}
|
s.name = %q{foreman}
|
||||||
s.version = "0.1.0"
|
s.version = "0.3.1"
|
||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
s.authors = ["David Dollar"]
|
s.authors = ["David Dollar"]
|
||||||
s.date = %q{2010-05-21}
|
s.date = %q{2010-06-22}
|
||||||
s.default_executable = %q{foreman}
|
s.default_executable = %q{foreman}
|
||||||
s.description = %q{Process manager for applications with multiple components}
|
s.description = %q{Process manager for applications with multiple components}
|
||||||
s.email = %q{ddollar@gmail.com}
|
s.email = %q{ddollar@gmail.com}
|
||||||
@@ -63,6 +63,7 @@ Gem::Specification.new do |s|
|
|||||||
s.add_development_dependency(%q<rcov>, ["~> 0.9.8"])
|
s.add_development_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||||
s.add_development_dependency(%q<rr>, ["~> 0.10.11"])
|
s.add_development_dependency(%q<rr>, ["~> 0.10.11"])
|
||||||
s.add_development_dependency(%q<rspec>, ["~> 2.0.0"])
|
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"])
|
s.add_runtime_dependency(%q<thor>, ["~> 0.13.6"])
|
||||||
else
|
else
|
||||||
s.add_dependency(%q<fakefs>, ["~> 0.2.1"])
|
s.add_dependency(%q<fakefs>, ["~> 0.2.1"])
|
||||||
@@ -70,6 +71,7 @@ Gem::Specification.new do |s|
|
|||||||
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||||
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
||||||
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
|
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"])
|
s.add_dependency(%q<thor>, ["~> 0.13.6"])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -78,6 +80,7 @@ Gem::Specification.new do |s|
|
|||||||
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||||
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
||||||
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
|
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"])
|
s.add_dependency(%q<thor>, ["~> 0.13.6"])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
module Foreman
|
module Foreman
|
||||||
|
|
||||||
VERSION = "0.1.0"
|
VERSION = "0.3.1"
|
||||||
|
|
||||||
class AppDoesNotExist < Exception; end
|
class AppDoesNotExist < Exception; end
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,20 @@ class Foreman::CLI < Thor
|
|||||||
Foreman::Engine.new(procfile).start
|
Foreman::Engine.new(procfile).start
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "execute PROCESS [PROCFILE]", "Run an instance of the specified process from PROCFILE"
|
||||||
|
|
||||||
|
def execute(process, procfile="Procfile")
|
||||||
|
error "#{procfile} does not exist." unless procfile_exists?(procfile)
|
||||||
|
Foreman::Engine.new(procfile).execute(process)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "screen [PROCFILE]", "Run the app described in PROCFILE as screen windows"
|
||||||
|
|
||||||
|
def screen(procfile="Procfile")
|
||||||
|
error "#{procfile} does not exist." unless procfile_exists?(procfile)
|
||||||
|
Foreman::Engine.new(procfile).screen
|
||||||
|
end
|
||||||
|
|
||||||
desc "export APP [PROCFILE] [FORMAT]", "Export the app described in PROCFILE as APP to another FORMAT"
|
desc "export APP [PROCFILE] [FORMAT]", "Export the app described in PROCFILE as APP to another FORMAT"
|
||||||
|
|
||||||
def export(app, procfile="Procfile", format="upstart")
|
def export(app, procfile="Procfile", format="upstart")
|
||||||
|
|||||||
+59
-18
@@ -1,11 +1,18 @@
|
|||||||
require "foreman"
|
require "foreman"
|
||||||
require "foreman/process"
|
require "foreman/process"
|
||||||
|
require "pty"
|
||||||
|
require "tempfile"
|
||||||
|
require "term/ansicolor"
|
||||||
|
|
||||||
class Foreman::Engine
|
class Foreman::Engine
|
||||||
|
|
||||||
attr_reader :procfile
|
attr_reader :procfile
|
||||||
attr_reader :directory
|
attr_reader :directory
|
||||||
|
|
||||||
|
extend Term::ANSIColor
|
||||||
|
|
||||||
|
COLORS = [ cyan, yellow, green, magenta, on_blue ]
|
||||||
|
|
||||||
def initialize(procfile)
|
def initialize(procfile)
|
||||||
@procfile = read_procfile(procfile)
|
@procfile = read_procfile(procfile)
|
||||||
@directory = File.expand_path(File.dirname(procfile))
|
@directory = File.expand_path(File.dirname(procfile))
|
||||||
@@ -16,6 +23,7 @@ class Foreman::Engine
|
|||||||
procfile.split("\n").inject({}) do |hash, line|
|
procfile.split("\n").inject({}) do |hash, line|
|
||||||
next if line.strip == ""
|
next if line.strip == ""
|
||||||
process = Foreman::Process.new(*line.split(" ", 2))
|
process = Foreman::Process.new(*line.split(" ", 2))
|
||||||
|
process.color = next_color
|
||||||
hash.update(process.name => process)
|
hash.update(process.name => process)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -31,37 +39,66 @@ class Foreman::Engine
|
|||||||
trap("TERM") { kill_and_exit("TERM") }
|
trap("TERM") { kill_and_exit("TERM") }
|
||||||
trap("INT") { kill_and_exit("INT") }
|
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
|
end
|
||||||
|
|
||||||
private ######################################################################
|
private ######################################################################
|
||||||
|
|
||||||
def fork(process)
|
def fork(process)
|
||||||
pid = Process.fork do
|
pid = Process.fork do
|
||||||
proctitle "ruby: foreman #{process.name}"
|
run(process)
|
||||||
|
|
||||||
Dir.chdir directory do
|
|
||||||
FileUtils.mkdir_p "log"
|
|
||||||
system "#{process.command} >>log/#{process.name}.log 2>&1"
|
|
||||||
exit $?.exitstatus || 255
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
info "started with pid #{pid}", process
|
info "started with pid #{pid}", process
|
||||||
running_processes[pid] = process
|
running_processes[pid] = process
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(process, log_to_file=true)
|
||||||
|
proctitle "ruby: foreman #{process.name}"
|
||||||
|
|
||||||
|
Dir.chdir directory do
|
||||||
|
FileUtils.mkdir_p "log"
|
||||||
|
command = process.command
|
||||||
|
|
||||||
|
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
|
||||||
|
until stdin.eof?
|
||||||
|
info stdin.gets, process
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def kill_and_exit(signal="TERM")
|
def kill_and_exit(signal="TERM")
|
||||||
info "termination requested"
|
info "terminating"
|
||||||
running_processes.each do |pid, process|
|
running_processes.each do |pid, process|
|
||||||
info "killing pid #{pid}", process
|
info "killing #{process.name} in pid #{pid}"
|
||||||
Process.kill(signal, pid)
|
Process.kill(signal, pid)
|
||||||
end
|
end
|
||||||
exit 0
|
exit 0
|
||||||
end
|
end
|
||||||
|
|
||||||
def info(message, process=nil)
|
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")}] [#{process ? process.name : "system"}] #{message.chomp}"
|
||||||
|
print Term::ANSIColor.reset
|
||||||
|
puts
|
||||||
end
|
end
|
||||||
|
|
||||||
def print_info
|
def print_info
|
||||||
@@ -79,17 +116,21 @@ private ######################################################################
|
|||||||
File.read(procfile)
|
File.read(procfile)
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_loop
|
def watch_for_termination
|
||||||
while true
|
pid, status = Process.wait2
|
||||||
pid, status = Process.wait2
|
process = running_processes.delete(pid)
|
||||||
process = running_processes.delete(pid)
|
info "process terminated", process
|
||||||
info "exited with code #{status}", process
|
kill_and_exit
|
||||||
fork process
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def running_processes
|
def running_processes
|
||||||
@running_processes ||= {}
|
@running_processes ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def next_color
|
||||||
|
@current_color ||= -1
|
||||||
|
@current_color += 1
|
||||||
|
@current_color >= COLORS.length ? "" : COLORS[@current_color]
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ exec #{process.command} >>/var/log/#{app}/#{process.name}.log 2>&1
|
|||||||
end
|
end
|
||||||
|
|
||||||
engine.processes.each do |name, process|
|
engine.processes.each do |name, process|
|
||||||
config.scale(name, 1)
|
config.processes[name] ||= 1
|
||||||
end
|
end
|
||||||
config.write
|
config.write
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class Foreman::Process
|
|||||||
|
|
||||||
attr_reader :name
|
attr_reader :name
|
||||||
attr_reader :command
|
attr_reader :command
|
||||||
|
attr_accessor :color
|
||||||
|
|
||||||
def initialize(name, command)
|
def initialize(name, command)
|
||||||
@name = name
|
@name = name
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ describe "Foreman::CLI" do
|
|||||||
subject { Foreman::CLI.new }
|
subject { Foreman::CLI.new }
|
||||||
|
|
||||||
describe "start" do
|
describe "start" do
|
||||||
#let(:engine) { stub_engine }
|
describe "with a non-existent Procfile" do
|
||||||
|
|
||||||
describe "with a non-existent Procifile" do
|
|
||||||
it "prints an error" do
|
it "prints an error" do
|
||||||
mock_error(subject, "Procfile does not exist.") do
|
mock_error(subject, "Procfile does not exist.") do
|
||||||
dont_allow.instance_of(Foreman::Engine).start
|
dont_allow.instance_of(Foreman::Engine).start
|
||||||
@@ -27,8 +25,29 @@ describe "Foreman::CLI" do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "execute" 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
|
||||||
|
subject.execute("alpha")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with a Procfile" do
|
||||||
|
before(:each) { write_procfile }
|
||||||
|
|
||||||
|
it "runs successfully" do
|
||||||
|
dont_allow(subject).error
|
||||||
|
mock.instance_of(Foreman::Engine).execute("alpha")
|
||||||
|
subject.execute("alpha")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "export" do
|
describe "export" do
|
||||||
describe "with a non-existent Procifile" do
|
describe "with a non-existent Procfile" do
|
||||||
it "prints an error" do
|
it "prints an error" do
|
||||||
mock_error(subject, "Procfile does not exist.") do
|
mock_error(subject, "Procfile does not exist.") do
|
||||||
dont_allow.instance_of(Foreman::Engine).export
|
dont_allow.instance_of(Foreman::Engine).export
|
||||||
|
|||||||
@@ -25,8 +25,16 @@ describe "Foreman::Engine" 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).run_loop
|
mock(subject).watch_for_termination
|
||||||
subject.start
|
subject.start
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
require "fakefs/spec_helpers"
|
require "rubygems"
|
||||||
require "rspec"
|
require "rspec"
|
||||||
|
require "fakefs/safe"
|
||||||
|
require "fakefs/spec_helpers"
|
||||||
|
|
||||||
$:.unshift "lib"
|
$:.unshift "lib"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user