Add support for starting procfile in tmux session

This commit is contained in:
Henry Hsu
2012-03-05 15:31:52 -08:00
parent a87a882e60
commit f745b16217
9 changed files with 142 additions and 1 deletions

View File

@@ -18,4 +18,5 @@ group :development do
gem 'rr', '~> 1.0.2'
gem 'rspec', '~> 2.0'
gem "simplecov", :require => false
gem 'timecop'
end

View File

@@ -40,6 +40,7 @@ GEM
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
thor (0.14.6)
timecop (0.3.5)
win32console (1.3.0-x86-mingw32)
xml-simple (1.0.15)
@@ -58,4 +59,5 @@ DEPENDENCIES
rr (~> 1.0.2)
rspec (~> 2.0)
simplecov
timecop
win32console (~> 1.3.0)

8
bin/taskman Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env ruby
$:.unshift File.expand_path("../../lib", __FILE__)
require "foreman/cli"
Foreman::CLI.engine_class = Foreman::TmuxEngine
Foreman::CLI.start

View File

@@ -1,6 +1,7 @@
require "foreman"
require "foreman/helpers"
require "foreman/engine"
require "foreman/tmux_engine"
require "foreman/export"
require "shellwords"
require "thor"
@@ -10,6 +11,7 @@ class Foreman::CLI < Thor
include Foreman::Helpers
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
class_option :tmux, :type => :boolean, :aliases => "-t", :desc => "Run in tmux session"
desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
@@ -73,6 +75,17 @@ class Foreman::CLI < Thor
end
end
class << self
def new_engine(procfile, options)
@engine_class ||= options[:tmux] ? Foreman::TmuxEngine : Foreman::Engine
@engine_class.new(procfile, options)
end
def engine_class=(klass)
@engine_class = klass
end
end
private ######################################################################
def check_procfile!
@@ -80,7 +93,7 @@ private ######################################################################
end
def engine
@engine ||= Foreman::Engine.new(procfile, options)
@engine ||= self.class.new_engine(procfile, options)
end
def procfile

View File

@@ -0,0 +1,35 @@
require "foreman"
require "foreman/engine"
class Foreman::TmuxEngine < Foreman::Engine
attr_reader :procfile
attr_reader :session
def initialize(procfile, options={})
@procfile = Foreman::Procfile.new(procfile)
@options = options.dup
@session = Time.now.to_i
end
def start
assign_colors
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
%x{tmux new-session -d -s #{session}}
procfile.entries.each_with_index do |entry, index|
name = "#{entry.name}.#{concurrency[entry.name]}"
if index == 0
%x{tmux rename-window -t #{session}:#{index} #{name}}
else
%x{tmux new-window -t #{session}:#{index} -n #{name}}
end
%x{tmux pipe-pane -o -t #{session}:#{index} "gawk '{ printf \\"%%s\\", \\"#{$stdout.color(colors[entry.name])}\\"; print strftime(\\"%%H:%%M:%%S\\"), \\"#{pad_process_name(name)} | #{$stdout.color(:reset)}\\", \\$0; fflush(); }' >> /tmp/foreman.#{session}.log"}
%x{tmux send-keys -t #{session}:#{index} "#{entry.command}" C-m}
end
last_index = procfile.entries.length
%x{tmux new-window -t #{session}:#{last_index} -n all}
%x{tmux send-keys -t #{session}:#{last_index} "tail -f /tmp/foreman.#{session}.log" C-m}
Kernel.exec("tmux attach-session -t #{session}")
end
end

View File

@@ -46,6 +46,10 @@ Specify an alternate Procfile to load, implies \fB\-d\fR at the Procfile root\.
\fB\-p\fR, \fB\-\-port\fR
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
.
.TP
\fB\-t\fR, \fB\-\-tmux\fR
Runs the processes in a tmux session\. Creates one window for each process and an extra window containing the output of each window (requires gawk)\.
.
.P
\fBforeman run\fR is used to run one\-off commands using the same environment as your defined processes\.
.

View File

@@ -40,6 +40,10 @@ The following options control how the application is run:
Specify which port to use as the base for this application. Should be
a multiple of 1000.
* `-t`, `--tmux`:
Runs the processes in a tmux session. Creates one window for each process
and an extra window containing the output of each window (requires gawk).
`foreman run` is used to run one-off commands using the same environment
as your defined processes.

View File

@@ -0,0 +1,73 @@
require "spec_helper"
require "foreman/tmux_engine"
describe "Foreman::TmuxEngine", :fakefs do
subject { Foreman::TmuxEngine.new("Procfile", {}) }
let(:session) { Time.now.to_i }
before do
any_instance_of(Foreman::TmuxEngine) do |engine|
stub(engine).proctitle
stub(engine).termtitle
end
Timecop.freeze(Time.now)
end
after do
FileUtils.rm_f("foreman.#{session}.log")
Timecop.return
end
describe "initialize" do
describe "without an existing Procfile" do
it "raises an error" do
lambda { subject }.should raise_error
end
end
describe "with a Procfile" do
before { write_procfile }
it "reads the processes" do
subject.procfile["alpha"].command.should == "./alpha"
subject.procfile["bravo"].command.should == "./bravo"
end
end
end
describe "start" do
before do
write_procfile
@pid = fork do
exec("tmux start-server")
end
end
after do
Process.waitpid(@pid)
%x{tmux kill-session -t #{session} &> /dev/null}
end
it "creates a tmux session and attaches" do
%x{tmux has-session -t #{session} &> /dev/null}
$?.exitstatus.should == 1
mock(Kernel).exec("tmux attach-session -t #{session}")
subject.start
%x{tmux has-session -t #{session}}
$?.exitstatus.should == 0
%x{tmux list-windows -t #{session}}.split("\n").map { |window|
if window =~ /[0-9]+:\s(.+?)\s/
$1
end
}.should == ["alpha.1", "bravo.1", "all"]
sleep 1
%x{tmux kill-session -t #{session}}
output = %x[cat /tmp/foreman.#{session}.log]
output.should =~ /alpha\.1.+\.\/alpha: No such file or directory/
output.should =~ /bravo\.1.+\.\/bravo: No such file or directory/
end
end
end

View File

@@ -6,6 +6,7 @@ SimpleCov.start do
end
require "rspec"
require "timecop"
require "fakefs/safe"
require "fakefs/spec_helpers"