Compare commits

...
22 Commits
Author SHA1 Message Date
David Dollar 0f7eec061d Regenerated gemspec for version 0.3.2 2010-06-22 17:20:21 -04:00
David Dollar 27e53eb916 0.3.2 2010-06-22 17:20:15 -04:00
David Dollar b123e6b3c5 change the output format 2010-06-22 17:20:01 -04:00
David Dollar c2000484bb capture PTY::ChildExited 2010-06-22 17:19:47 -04:00
David Dollar 75f0ce4b9c Regenerated gemspec for version 0.3.1 2010-06-22 16:48:41 -04:00
David Dollar d508d44fd2 0.3.1 2010-06-22 16:48:39 -04:00
David Dollar 35cc880d40 clean up messaging 2010-06-22 16:48:16 -04:00
David Dollar 6434db289b 0.3.0 2010-06-22 16:33:48 -04:00
David Dollar 1a118ee92b fix up docs 2010-06-22 16:33:16 -04:00
David Dollar 7bd176063c fix up specs 2010-06-22 16:29:49 -04:00
David Dollar fff958f7b6 add colorization 2010-06-22 16:28:08 -04:00
David Dollar 7888ee8c52 print to stdout, die when any child process dies 2010-06-22 16:15:27 -04:00
David Dollar a6197c183e change example 2010-06-22 16:15:17 -04:00
David Dollar 41a0620126 Merge branch 'master' of github.com:ddollar/foreman 2010-06-11 21:52:07 -04:00
David Dollar 86654d7918 update docs 2010-06-10 15:27:18 -04:00
David Dollar 9929165d17 update docs 2010-06-10 15:26:28 -04:00
David Dollar 2b8d575aab 0.2.0 2010-06-09 12:32:33 -04:00
David Dollar 56f8603a5d first attempt at screen-based running 2010-06-09 12:13:02 -04:00
David Dollar 3f48d7c541 add execute command 2010-06-09 11:51:18 -04:00
David Dollar 675ad2630d cleanup 2010-06-09 11:51:10 -04:00
David Dollar 9004bf67e1 dont roll back to 1 every export 2010-05-21 15:38:35 -04:00
David Dollar 19c425d200 add gemcutter tasks 2010-05-21 15:32:51 -04:00
15 changed files with 159 additions and 50 deletions
+17 -9
View File
@@ -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
+3 -1
View File
@@ -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
+1 -2
View File
@@ -1,3 +1,2 @@
neverdie ./never_die ticker ./ticker
diealot ./die_alot
error ./error error ./error
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env ruby
puts "sleeping for 2s then dying"
sleep 2
exit 0
+1 -1
View File
@@ -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"
+1 -2
View File
@@ -2,6 +2,5 @@
while true while true
puts "tick" puts "tick"
$stdout.flush sleep 1
sleep 5
end end
+5 -2
View File
@@ -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.2"
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
View File
@@ -1,6 +1,6 @@
module Foreman module Foreman
VERSION = "0.1.0" VERSION = "0.3.2"
class AppDoesNotExist < Exception; end class AppDoesNotExist < Exception; end
+14
View File
@@ -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")
+75 -16
View File
@@ -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,84 @@ 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
begin
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
until stdin.eof?
info stdin.gets, process
end
end
rescue PTY::ChildExited
# exited
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")} #{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 end
def print_info def print_info
@@ -79,17 +134,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 "exited with code #{status}", process info "process terminated", process
fork process kill_and_exit
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
+1 -1
View File
@@ -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
+1
View File
@@ -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
+23 -4
View File
@@ -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
+9 -1
View File
@@ -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
View File
@@ -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"