Compare commits

..
2 Commits
Author SHA1 Message Date
David Dollar d0f5b2412c 0.7.4 2010-09-17 09:30:20 -04:00
David Dollar 15ab54bc18 include files from export in the gem 2010-09-17 09:30:09 -04:00
112 changed files with 419 additions and 3171 deletions
+7 -10
View File
@@ -1,10 +1,7 @@
/.bundle .bundle
/.rbenv-version coverage
/.yardoc example/log/*
/coverage man/*.?
/example/log/* man/*.html
/man/*.html man/*.markdown
/man/*.markdown pkg
/pkg
/tags
/vendor
-13
View File
@@ -1,13 +0,0 @@
script: bundle exec rake spec
rvm:
- 1.9.2
- 1.9.3
notifications:
email: false
webhooks:
on_success: always
on_failure: always
urls:
- http://dx-helper.herokuapp.com/travis
-206
View File
@@ -1,206 +0,0 @@
## 0.46.0 (2012-05-02)
* Add Profile load/write/append API [Michael Granger]
* Guard against missing Procfile in engine.rb [Brian Kaney]
## 0.45.0 (2012-04-26)
* create and chown log dir in upstart export. [Phil Hagelberg]
* remove parka from dist files [David Dollar]
## 0.44.0 (2012-04-23)
* make var output order repeatable in supervisord export [David Dollar]
* make --procfile and --app-root influence each other in a more intuitive way [David Dollar]
* Look for .env and app_root in the same dir as the Procfile. [Phil Hagelberg]
## 0.43.0 (2012-04-20)
* wrap supervisord env vars in quotes [Raphael Randschau]
## 0.42.0 (2012-04-18)
* Move read_environment to a public class method. [Phil Hagelberg]
* Drop parka dependency [Phil Hagelberg]
* add group support for supervisord [Raphael Randschau]
* fix enviroment export [Raphael Randschau]
## 0.41.0 (2012-03-16)
* replace term-ansicolor with built-in colorization [David Dollar]
* supervisord export template [Raphael Randschau]
## 0.40.0 (2012-02-24)
* support various quoting styles in .env [David Dollar]
* remove load_env! as it's made unnecessary by foreman run [David Dollar]
* Provide a useful error if `foreman check` fails to find a Procfile [R. Tyler Croy]
* update docs [David Dollar]
## 0.39.0 (2012-02-07)
* rename bin/runner to bin/foreman-runner [David Dollar]
* fix tgz release [David Dollar]
* bundle update hpricot [John Firebaugh]
* touch up .pkg release tasks [David Dollar]
## 0.38.0 (2012-02-02)
* bring back single process starting [David Dollar]
* more attempts at getting ci working with jruby [David Dollar]
* ignore .rbenv-version [David Dollar]
* force to binary encoding if supported [David Dollar]
## 0.37.2 (2012-01-29)
* handle directories with spaces in runner [David Dollar]
* update docs [David Dollar]
## 0.37.1 (2012-01-29)
* use binary pipes to better handle UTF-8 data [David Dollar]
* set up example procfile with UTF-8 item [David Dollar]
* remove autotest [David Dollar]
* fix up authors generation [David Dollar]
* fix up packaging after moving tasks [David Dollar]
* fix up changelog tasks [David Dollar]
## 0.37.0 (2012-01-29)
* put an entire line of output inside a single mutex so we don't cross the streams [David Dollar]
* fix race condition with process termination [David Dollar]
* allow external custom exporters [Chris Lowder]
* fix the test for an empty string in bin/runner [Florian Apolloner]
* ensure we have non-nil data, fixes #111 [David Dollar]
* make sure error method exists, fixes #104 [David Dollar]
* clean up chdir usage [David Dollar]
* normalize platform names [David Dollar]
* add windows support [David Dollar]
* add jruby support [David Dollar]
* pass basedir along to the runner script [David Dollar]
* harden runner script [David Dollar]
* add many missing specs [brainopia]
* clean up fakefs usage in specs [brainopia]
* runit creates a full path to export directory. [Fletcher Nichol]
## 0.36.1 (2012-01-18)
* 0.36.1 [David Dollar]
* bump term-ansicolor in gemspec [David Dollar]
## 0.36.0 (2012-01-17)
* 0.36.0 [David Dollar]
* sync the writer stream [David Dollar]
* capture stderr as well [David Dollar]
## 0.35.0 (2012-01-16)
* update rake [David Dollar]
* 0.35.0 [David Dollar]
* Merge pull request #132 from Viximo/feature/concurrency [David Dollar]
* Fix export specs [Matt Griffin]
* Merge branch 'master' of https://github.com/michaeldwan/foreman into feature/concurrency [Matt Griffin]
* default process concurrency is 0 when concurrency options specified, otherwise default concurrency is 1 [Michael Dwan]
## 0.34.1 (2012-01-16)
* 0.34.1 [David Dollar]
* fix missing start desc [David Dollar]
## 0.34.0 (2012-01-16)
* 0.34.0 [David Dollar]
* update man page [David Dollar]
* update docs for -d [David Dollar]
* Merge pull request #101 from ndbroadbent/foreman [David Dollar]
* Wrap around to the first colour when all the colours are used [Craig R Webster]
* run specs in random order [David Dollar]
* update rspec [David Dollar]
* pedantry [David Dollar]
* Set executable bit on runit run scripts. [Matthijs Langenberg]
* Merge pull request #114 from gburt/master [David Dollar]
* add more colors [Gabriel Burt]
* Added option to specify app_root, if executing a Procfile from a shared location [Nathan Broadbent]
## 0.33.1 (2012-01-16)
* 0.33.1 [David Dollar]
* Merge pull request #129 from fnichol/resolve-home-template [David Dollar]
* Expand template path under user's home directory. [Fletcher Nichol]
## 0.33.0 (2012-01-15)
* 0.33.0 [David Dollar]
* Revert "Merge pull request #125 from brainopia/master" [David Dollar]
## 0.32.0 (2012-01-12)
* 0.32.0 [David Dollar]
* Merge pull request #125 from brainopia/master [David Dollar]
* Merge pull request #121 from Viximo/feature/run [David Dollar]
* Return some whitespace that was accidentally removed [Matt Griffin]
* Steal the run method back from Thor so that it can be used in place for exec for running commands in the foreman environment. [Matt Griffin]
* Remove old cruft [brainopia]
* In case someone wants to use bin/runner directly [brainopia]
* Fix for double fork [brainopia]
* Use ruby exec which works with escaped cmd and replaces shell [brainopia]
* Fix foreman to work with cmds containing pipes and redirects [brainopia]
* Add "exec" action to allow execution of arbitrary commands with the app's environment. [Matt Griffin]
* tweak readme [David Dollar]
## 0.31.0 (2012-01-04)
* 0.31.0 [David Dollar]
* make fork more robust [David Dollar]
* remove unnecessary debug [David Dollar]
* add more information when shutting down [David Dollar]
* Merge pull request #110 from lstoll/master [David Dollar]
* Use different port ranges for each process type [Lincoln Stoll]
## 0.30.1 (2011-12-23)
* 0.30.1 [David Dollar]
* require thread for mutex [David Dollar]
## 0.30.0 (2011-12-22)
* 0.30.0 [David Dollar]
* compatibility with ruby 1.8 [David Dollar]
## 0.29.0 (2011-12-22)
* 0.29.0 [David Dollar]
* 0.28.0.pre2 [David Dollar]
* fix pipe error [David Dollar]
* 0.28.0.pre1 [David Dollar]
* Merge branch 'fork' [David Dollar]
* wip [David Dollar]
* wip [David Dollar]
* wip [David Dollar]
* wip [David Dollar]
* wip [David Dollar]
## 0.27.0 (2011-12-05)
* 0.27.0 [David Dollar]
* add changelog [David Dollar]
* Merge pull request #103 from csquared/load_env_from_irb [David Dollar]
* refactor load_env to apply_environment [Chris Continanza]
* rename load! to load_env! [Chris Continanza]
* use ./.env as default [Chris Continanza]
* load contents from env file [Chris Continanza]
* refactor engine to expose env methods [Chris Continanza]
* disable email notifications [David Dollar]
* add travis config [David Dollar]
## 0.26.1 2011-12-05
* Merge pull request #103 from csquared/load_env_from_irb [David Dollar]
* refactor load_env to apply_environment [Chris Continanza]
* rename load! to load_env! [Chris Continanza]
* use ./.env as default [Chris Continanza]
* load contents from env file [Chris Continanza]
* refactor engine to expose env methods [Chris Continanza]
* disable email notifications [David Dollar]
* add travis config [David Dollar]
+11 -18
View File
@@ -1,23 +1,16 @@
source "http://rubygems.org" source "http://rubygems.org"
gemspec
platform :mingw do
gem "win32console", "~> 1.3.0"
end
platform :jruby do
gem "posix-spawn", "~> 0.3.6"
end
group :development do group :development do
gem 'aws-s3' gem 'parka'
gem 'rake' gem 'rake'
gem 'ronn'
gem 'fakefs', '~> 0.3.2'
gem 'rr', '~> 1.0.2'
gem 'rspec', '~> 2.0'
gem "simplecov", :require => false
gem 'timecop'
gem 'yard'
end end
group :test do
gem 'fakefs', '~> 0.2.1'
gem 'rcov', '~> 0.9.8'
gem 'rr', '~> 0.10.11'
gem 'rspec', '~> 2.0.0.beta.19'
end
gem 'term-ansicolor', '~> 1.0.5'
gem 'thor', '~> 0.13.6'
+27 -55
View File
@@ -1,65 +1,37 @@
PATH
remote: .
specs:
foreman (0.47.0)
thor (>= 0.13.6)
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
aws-s3 (0.6.2) diff-lcs (1.1.2)
builder fakefs (0.2.1)
mime-types
xml-simple
builder (3.0.0)
diff-lcs (1.1.3)
fakefs (0.3.2)
hpricot (0.8.6)
hpricot (0.8.6-java)
mime-types (1.16) mime-types (1.16)
multi_json (1.0.4) parka (0.3.1)
mustache (0.11.2) rest-client
posix-spawn (0.3.6) thor
rake (0.9.2.2) rake (0.8.7)
rdiscount (1.6.5) rcov (0.9.8)
ronn (0.7.3) rest-client (1.6.0)
hpricot (>= 0.8.2) mime-types (>= 1.16)
mustache (>= 0.7.0) rr (0.10.11)
rdiscount (>= 1.5.8) rspec (2.0.0.beta.19)
rr (1.0.2) rspec-core (= 2.0.0.beta.19)
rspec (2.8.0) rspec-expectations (= 2.0.0.beta.19)
rspec-core (~> 2.8.0) rspec-mocks (= 2.0.0.beta.19)
rspec-expectations (~> 2.8.0) rspec-core (2.0.0.beta.19)
rspec-mocks (~> 2.8.0) rspec-expectations (2.0.0.beta.19)
rspec-core (2.8.0) diff-lcs (>= 1.1.2)
rspec-expectations (2.8.0) rspec-mocks (2.0.0.beta.19)
diff-lcs (~> 1.1.2) term-ansicolor (1.0.5)
rspec-mocks (2.8.0) thor (0.13.8)
simplecov (0.5.4)
multi_json (~> 1.0.3)
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)
yard (0.8.2)
PLATFORMS PLATFORMS
java
ruby ruby
x86-mingw32
DEPENDENCIES DEPENDENCIES
aws-s3 fakefs (~> 0.2.1)
fakefs (~> 0.3.2) parka
foreman!
posix-spawn (~> 0.3.6)
rake rake
ronn rcov (~> 0.9.8)
rr (~> 1.0.2) rr (~> 0.10.11)
rspec (~> 2.0) rspec (~> 2.0.0.beta.19)
simplecov term-ansicolor (~> 1.0.5)
timecop thor (~> 0.13.6)
win32console (~> 1.3.0)
yard
+4
View File
@@ -0,0 +1,4 @@
Foreman
=======
See the [man page](http://ddollar.github.com/foreman) for usage.
-46
View File
@@ -1,46 +0,0 @@
# Foreman
Manage Procfile-based applications
<table>
<tr>
<th>If you have...</th>
<th>Install with...</th>
</tr>
<tr>
<td>Ruby (MRI, JRuby, Windows)</td>
<td><pre>$ gem install foreman</pre></td>
</tr>
<tr>
<td>Mac OS X</td>
<td><a href="http://assets.foreman.io/foreman/foreman.pkg">foreman.pkg</a></td>
</tr>
</table>
## Getting Started
* http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
## Documentation
* [man page](http://ddollar.github.com/foreman)
* [wiki](http://github.com/ddollar/foreman/wiki)
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
## Ports
* [shoreman](https://github.com/hecticjeff/shoreman) - shell
* [honcho](https://github.com/nickstenning/honcho) - python
* [norman](https://github.com/josh/norman) - node.js
## Authors
#### Created and maintained by
David Dollar
#### Patches contributed by
[Contributor List](https://github.com/ddollar/foreman/contributors)
## License
MIT
+46 -3
View File
@@ -1,8 +1,51 @@
require "rubygems"
require "bundler"
Bundler.setup
require "rake"
require "rspec"
require "rspec/core/rake_task"
$:.unshift File.expand_path("../lib", __FILE__) $:.unshift File.expand_path("../lib", __FILE__)
require "foreman" require "foreman"
require "bundler/setup" task :default => :spec
task :release => :man
Dir[File.expand_path("../tasks/*.rake", __FILE__)].each do |task| desc "Run all specs"
load task Rspec::Core::RakeTask.new(:spec) do |t|
t.pattern = 'spec/**/*_spec.rb'
end
desc "Generate RCov code coverage report"
task :rcov => "rcov:build" do
%x{ open coverage/index.html }
end
Rspec::Core::RakeTask.new("rcov:build") do |t|
t.pattern = 'spec/**/*_spec.rb'
t.rcov = true
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 "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 end
+1
View File
@@ -0,0 +1 @@
Autotest.add_discovery { "rspec2" }
-32
View File
@@ -1,32 +0,0 @@
#!/bin/sh
#
#/ Usage: foreman-runner [-d <dir>] <command> [<args>...]
#/
#/ Run a command with exec, optionally changing directory first
set -e
error() {
echo $@ >&2
exit 1
}
usage() {
cat $0 | grep '^#/' | cut -c4-
exit
}
while getopts ":hd:" OPT; do
case $OPT in
d) cd "$OPTARG" ;;
h) usage ;;
\?) error "invalid option: -$OPTARG" ;;
:) error "option -$OPTARG requires an argument" ;;
esac
done
shift $((OPTIND-1))
[ -z "$1" ] && usage
exec "$@"
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env ruby
$:.unshift File.expand_path("../../lib", __FILE__)
require "foreman/cli"
Foreman::CLI.engine_class = Foreman::TmuxEngine
Foreman::CLI.start
-4
View File
@@ -1,4 +0,0 @@
ticker: ruby ./ticker $PORT
error: ruby ./error
utf8: ruby ./utf8
spawner: ./spawner
-4
View File
@@ -1,4 +0,0 @@
tick
tick
./never_die:6:in `sleep': Interrupt
from ./never_die:6
-14
View File
@@ -1,14 +0,0 @@
#!/bin/sh
NAME="$1"
sigterm() {
echo "$NAME: got sigterm"
}
#trap sigterm SIGTERM
while true; do
echo "$NAME: ping"
sleep 1
done
-7
View File
@@ -1,7 +0,0 @@
#!/bin/sh
./spawnee A &
./spawnee B &
./spawnee C &
wait
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env ruby
$stdout.sync = true
%w( SIGINT SIGTERM ).each do |signal|
trap(signal) do
puts "received #{signal} but i'm ignoring it!"
end
end
while true
puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
sleep 1
end
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env ruby
# encoding: BINARY
$stdout.sync = true
while true
puts "\u65e5\u672c\u8a9e\u6587\u5b57\u5217"
puts "\u0915\u0932\u094d\u0907\u0928\u0643\u0637\u0628\u041a\u0430\u043b\u0438\u043d\u0430"
puts "\xff\x03"
sleep 1
end
-28
View File
@@ -1,28 +0,0 @@
Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "<%= user %>"
app.gid = "<%= user %>"
<% engine.each_process do |name, process| %>
<% 1.upto(engine.formation[name]) do |num| %>
<% port = engine.port_for(process, num) %>
app.process("<%= name %>-<%= num %>") do |process|
process.start_command = "<%= process.command %>"
process.working_dir = "<%= engine.root %>"
process.daemonize = true
process.environment = <%= engine.env.merge("PORT" => port.to_s).inspect %>
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "<%= log %>/<%= app %>-<%= name %>-<%= num %>.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "<%= app %>-<%= name %>"
end
<% end %>
<% end %>
end
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string><%= "#{app}-#{name}-#{num}" %></string>
<key>ProgramArguments</key>
<array>
<string><%= process.command %></string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
<key>UserName</key>
<string><%= user %></string>
<key>WorkingDirectory</key>
<string><%= engine.root %></string>
</dict>
</plist>
-7
View File
@@ -1,7 +0,0 @@
#!/bin/sh
set -e
LOG=<%= log %>/<%= name %>-<%= num %>
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
exec chpst -u <%= user %> svlogd "$LOG"
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
cd <%= engine.root %>
exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %>
-27
View File
@@ -1,27 +0,0 @@
<%
app_names = []
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
full_name = "#{app}-#{name}-#{num}"
environment = engine.env.merge("PORT" => port.to_s).map do |key, value|
"#{key}=#{shell_quote(value)}"
end
app_names << full_name
%>
[program:<%= full_name %>]
command=<%= process.command %>
autostart=true
autorestart=true
stopsignal=QUIT
stdout_logfile=<%= log %>/<%= name %>-<%= num %>.log
stderr_logfile=<%= log %>/<%= name %>-<%= num %>.error.log
user=<%= user %>
directory=<%= engine.root %>
environment=<%= environment.join(',') %><%
end
end
%>
[group:<%= app %>]
programs=<%= app_names.join(',') %>
-8
View File
@@ -1,8 +0,0 @@
pre-start script
bash << "EOF"
mkdir -p <%= log %>
chown -R <%= user %> <%= log %>
EOF
end script
-5
View File
@@ -1,5 +0,0 @@
start on starting <%= app %>-<%= name %>
stop on stopping <%= app %>-<%= name %>
respawn
exec su - <%= user %> -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'
-33
View File
@@ -1,33 +0,0 @@
file pkg("/apt-#{version}/foreman-#{version}.deb") => distribution_files("deb") do |t|
mkchdir(File.dirname(t.name)) do
mkchdir("usr/local/foreman") do
assemble_distribution
assemble_gems
assemble resource("deb/foreman"), "bin/foreman", 0755
File.chmod 0755, "bin/foreman-runner"
end
assemble resource("deb/control"), "control"
assemble resource("deb/postinst"), "postinst"
sh "tar czvf data.tar.gz usr/local/foreman --owner=root --group=root"
sh "tar czvf control.tar.gz control postinst"
File.open("debian-binary", "w") do |f|
f.puts "2.0"
end
deb = File.basename(t.name)
sh "ar -r #{t.name} debian-binary control.tar.gz data.tar.gz"
end
end
desc "Build a .deb package"
task "deb:build" => pkg("/apt-#{version}/foreman-#{version}.deb")
desc "Remove build artifacts for .deb"
task "deb:clean" do
clean pkg("foreman-#{version}.deb")
FileUtils.rm_rf("pkg/apt-#{version}") if Dir.exists?("pkg/apt-#{version}")
end
-14
View File
@@ -1,14 +0,0 @@
file pkg("foreman-#{version}.gem") => distribution_files do |t|
sh "gem build foreman.gemspec"
sh "mv foreman-#{version}.gem #{t.name}"
end
task "gem:build" => pkg("foreman-#{version}.gem")
task "gem:clean" do
clean pkg("foreman-#{version}.gem")
end
task "gem:release" => "gem:build" do |t|
sh "gem push #{pkg("foreman-#{version}.gem")} || echo 'error'"
end
-16
View File
@@ -1,16 +0,0 @@
file pkg("foreman-#{version}-jruby.gem") => distribution_files do |t|
Bundler.with_clean_env do
sh "env PLATFORM=java gem build foreman.gemspec"
end
sh "mv foreman-#{version}-java.gem #{t.name}"
end
task "jruby:build" => pkg("foreman-#{version}-jruby.gem")
task "jruby:clean" do
clean pkg("foreman-#{version}-jruby.gem")
end
task "jruby:release" => "jruby:build" do |t|
sh "gem push #{pkg("foreman-#{version}-jruby.gem")} || echo 'error'"
end
-16
View File
@@ -1,16 +0,0 @@
file pkg("foreman-#{version}-mingw32.gem") => distribution_files do |t|
Bundler.with_clean_env do
sh "env PLATFORM=mingw32 gem build foreman.gemspec"
end
sh "mv foreman-#{version}-mingw32.gem #{t.name}"
end
task "mingw32:build" => pkg("foreman-#{version}-mingw32.gem")
task "mingw32:clean" do
clean pkg("foreman-#{version}-mingw32.gem")
end
task "mingw32:release" => "mingw32:build" do |t|
sh "gem push #{pkg("foreman-#{version}-mingw32.gem")} || echo 'error'"
end
-53
View File
@@ -1,53 +0,0 @@
require "erb"
file pkg("foreman-#{version}.pkg") => distribution_files do |t|
tempdir do |dir|
mkchdir("foreman") do
assemble_distribution
assemble_gems
assemble resource("pkg/foreman"), "bin/foreman", 0755
end
kbytes = %x{ du -ks foreman | cut -f 1 }
num_files = %x{ find foreman | wc -l }
mkdir_p "pkg"
mkdir_p "pkg/Resources"
mkdir_p "pkg/foreman.pkg"
dist = File.read(resource("pkg/Distribution.erb"))
dist = ERB.new(dist).result(binding)
File.open("pkg/Distribution", "w") { |f| f.puts dist }
dist = File.read(resource("pkg/PackageInfo.erb"))
dist = ERB.new(dist).result(binding)
File.open("pkg/foreman.pkg/PackageInfo", "w") { |f| f.puts dist }
mkdir_p "pkg/foreman.pkg/Scripts"
cp resource("pkg/postinstall"), "pkg/foreman.pkg/Scripts/postinstall"
chmod 0755, "pkg/foreman.pkg/Scripts/postinstall"
sh %{ mkbom -s foreman pkg/foreman.pkg/Bom }
Dir.chdir("foreman") do
sh %{ pax -wz -x cpio . > ../pkg/foreman.pkg/Payload }
end
sh %{ pkgutil --flatten pkg foreman-#{version}.pkg }
FileUtils.mkdir_p(File.dirname(t.name))
cp_r "foreman-#{version}.pkg", t.name
end
end
task "pkg:build" => pkg("foreman-#{version}.pkg")
task "pkg:clean" do
clean pkg("foreman-#{version}.pkg")
end
task "pkg:release" => "pkg:build" do |t|
store pkg("foreman-#{version}.pkg"), "foreman/foreman-#{version}.pkg"
store pkg("foreman-#{version}.pkg"), "foreman/foreman-beta.pkg" if beta?
store pkg("foreman-#{version}.pkg"), "foreman/foreman.pkg" unless beta?
end
-12
View File
@@ -1,12 +0,0 @@
Package: foreman
Version: <%= version %>
Section: main
Priority: standard
Architecture: all
Depends: ruby1.9.1
Maintainer: Heroku
Description: Manage Procfile-based applications.
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.
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/env ruby1.9.1
# resolve bin path, ignoring symlinks
require "pathname"
bin_file = Pathname.new(__FILE__).realpath
# add locally vendored gems to libpath
gem_dir = File.expand_path("../../vendor/gems", bin_file)
Dir["#{gem_dir}/**/lib"].each do |libdir|
$:.unshift libdir
end
# add self to libpath
$:.unshift File.expand_path("../../lib", bin_file)
require "foreman/cli"
Foreman::CLI.start
-30
View File
@@ -1,30 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (Darwin)
mQENBE5SfAEBCADLp056ZgfdtAMXLWpEuL9zY+dIHIY5qLQcDmUivjHLVE4l3Bi3
Mn570K0W9rfk7fHBPEO2XJEDdjk8Bg6mWTAeGjdfZgZaL+qO9NjqQ5QmVR+vgp7s
yxJYlfY+JYTZvl/JiDWGhuPHSPggXILCMf3SpqWMHGPqe/3RAK+CHCNv/94uaoS4
vi4HQT+k4sRceiM8WqkSRYSoc7rzdDejZn+InCYFfR56VeSFF4G4I6neZs/q5T9d
Ty2i5d0gZLaX/Iqc+3Dy0vDKClc0HUQJ6ajDPuUqKLHFUpqyuwfJij60+C3GMi8K
ckRPti31EPFVzq3GPHU+GqA+e9j84WHr4uJ5ABEBAAG0L0hlcm9rdSBSZWxlYXNl
IEVuZ2luZWVyaW5nIDxyZWxlYXNlQGhlcm9rdS5jb20+iQE4BBMBAgAiBQJOUnwB
AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDJJ+vgDxsFIChECAC9h4Ay
Nx4AQFu85cjR9rijyBflPeVqi7Xhzd7IvLg2+kZSexlb2oidj7iVSMy+vy5tG9g9
8Az/JqMCVjcZ7ltn60OGU8gIYpJqt6VmH3vfJBxXu/Sm9tym3UCYGVvMAN5Oq6yB
HlQkQ8F3p0cW69PmF+fibkgo9RE0EYlBIt2rUHNilTGFS6vXGr5reFFp3/rRHq3k
bixnUwFSqNujJgnBKDPwtSYKc4pMpnhuv88xEpLH7vU8NLXQZMitKQguV8XEmcsu
43LXlsx5uVr239/XNW+h412gIHFDSzB/YuLWlVUXMfquC96z/wxMqWWZyskDNgr0
WDdMgzK6CUfXSqQhuQENBE5SfAEBCADbnGKcXpdVauQpINQLtRnrT0BJIrIo1Yxv
LQRb3G7RU+Eq6aHXwk9fSKa6nEv9RsmqiW874yODnr0d/DTUWMHT+jRvPHm1wlbE
pGR1aPSo7GgkSUdaT6CVBN3JWZ2kVJGqohNoJMYbfVaWd/kpa/LiMFWzS8LfWT2K
xiO2vIh4qBfeRCGR7s8rADCHuHJ0eibADrgqcRfdPrChB1JiYLeTdV4yRmSzJ7TM
zWX7OVpGfIFLbCw9NeN65pI9ePs2mSPM7DYkhhKSXWMwJNXFzn1blOGiwAwKb48P
a/QpE6TG3PQzbYyTTP0Td1XgKAHcprvbc89a/nAk3a+PJQ/MqvDzABEBAAGJAR8E
GAECAAkFAk5SfAECGwwACgkQySfr4A8bBSD4mAgAnCT5WRiDl0259Px9Z9J9Wk8Z
SxugDct2Yhzca4aw1Ou4cfaIFCDXzFlBzSJfqk0HoVhp9r2gzEPUCKnSjRDyxaMo
wZCUtqigBua+z4NB4AWgeOl/2S06I2ki1K7pfl4piYcHtEThHamnhVPJ2Hi6HsHq
mUU+8SxleHE4GCXmKkuvxelUq9jrhHikIkm1RoqFOPb9zV3WRy4YzVHQSYfHmfk0
9kXlM/CS0sfNv2UKCX+5e6eFIZv0rdtpp6VEh0tsFmsIClY6Z9MX7bgp8MnUJpyk
OeIzOzQgkb4aeT0Whl+EPcTeDZfqIhVBoNXupUanmWNppFcMngxfqG2NGi1vvQ==
=aUAq
-----END PGP PUBLIC KEY BLOCK-----
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
set -e
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman
-23
View File
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<installer-script minSpecVersion="1.000000" authoringTool="org.ruby-lang.rake" authoringToolVersion="0.8.7">
<title>Foreman</title>
<options customize="never" allow-external-scripts="no"/>
<domains enable_localSystem="true"/>
<script><![CDATA[
function needs_git() {
return (system.compareVersion(system.version.ProductVersion, "10.7.0") < 0);
}
]]></script>
<choices-outline>
<line choice="git"/>
<line choice="foreman"/>
</choices-outline>
<choice id="git" title="git" start_selected="false" start_enabled="false" selected="needs_git()" enabled="needs_git()">
<pkg-ref id="git.pkg" />
</choice>
<choice id="foreman" title="foreman">
<pkg-ref id="io.foreman.installer"/>
</choice>
<pkg-ref id="io.foreman.installer" installKBytes="<%= kbytes %>" version="<%= version %>" auth="Root">#foreman.pkg</pkg-ref>
</installer-script>
-6
View File
@@ -1,6 +0,0 @@
<pkg-info format-version="2" identifier="io.foreman.installer" version="<%= version %>" install-location="/usr/local/foreman" auth="root">
<payload installKBytes="<%= kbytes %>" numberOfFiles="<%= num_files %>"/>
<scripts>
<postinstall file="./postinstall"/>
</scripts>
</pkg-info>
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/ruby
require "pathname"
bin_file = Pathname.new(__FILE__).realpath
gem_dir = File.expand_path("../../vendor/gems", bin_file)
Dir["#{gem_dir}/**/lib"].each do |libdir|
$:.unshift libdir
end
$:.unshift File.expand_path("../../lib", bin_file)
require "foreman/cli"
Foreman::CLI.start
-2
View File
@@ -1,2 +0,0 @@
#!/bin/sh
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env ruby
require "pathname"
bin_file = Pathname.new(__FILE__).realpath
gem_dir = File.expand_path("../vendor/gems", bin_file)
Dir["#{gem_dir}/**/lib"].each do |libdir|
$:.unshift libdir
end
$:.unshift File.expand_path("../lib", bin_file)
require "foreman/cli"
Foreman::CLI.start
-24
View File
@@ -1,24 +0,0 @@
file pkg("foreman-#{version}.tgz") => distribution_files do |t|
tempdir do |dir|
mkchdir("foreman") do
assemble_distribution
assemble_gems
rm_f "bin/foreman"
assemble resource("tgz/foreman"), "foreman", 0755
end
sh "tar czvf #{t.name} foreman"
end
end
task "tgz:build" => pkg("foreman-#{version}.tgz")
task "tgz:clean" do
clean pkg("foreman-#{version}.tgz")
end
task "tgz:release" => "tgz:build" do |t|
store pkg("foreman-#{version}.tgz"), "foreman/foreman-#{version}.tgz"
store pkg("foreman-#{version}.tgz"), "foreman/foreman-beta.tgz" if beta?
store pkg("foreman-#{version}.tgz"), "foreman/foreman.tgz" unless beta?
end
-2
View File
@@ -1,7 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
$stdout.sync = true
puts "will error in 10s" puts "will error in 10s"
sleep 5 sleep 5
raise "Dying" raise "Dying"
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
while true
puts "tick: #{ARGV.inspect}"
sleep 1
end
+8
View File
@@ -0,0 +1,8 @@
pre-start script
bash << "EOF"
mkdir -p <%= log_root %>
chown -R <%= user %> <%= log_root %>
EOF
end script
+6
View File
@@ -0,0 +1,6 @@
start on starting <%= app %>-<%= process.name %>
stop on stopping <%= app %>-<%= process.name %>
respawn
chdir <%= engine.directory %>
exec su <%= user %> -c "PORT=<%= port %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1"
+7 -24
View File
@@ -1,30 +1,13 @@
$:.unshift File.expand_path("../lib", __FILE__) require "rubygems"
require "foreman/version" require "parka/specification"
Gem::Specification.new do |gem| Parka::Specification.new do |gem|
gem.name = "foreman" gem.name = "foreman"
gem.version = Foreman::VERSION gem.version = Foreman::VERSION
gem.author = "David Dollar"
gem.email = "ddollar@gmail.com"
gem.homepage = "http://github.com/ddollar/foreman"
gem.summary = "Process manager for applications with multiple components" gem.summary = "Process manager for applications with multiple components"
gem.homepage = "http://github.com/ddollar/foreman"
gem.description = gem.summary gem.executables = "foreman"
gem.files << "man/foreman.1"
gem.executables = "foreman" gem.files << Dir["export/**/*"]
gem.files = Dir["**/*"].select { |d| d =~ %r{^(README|bin/|data/|ext/|lib/|spec/|test/)} }
gem.files << "man/foreman.1"
gem.add_dependency 'thor', '>= 0.13.6'
if ENV["PLATFORM"] == "java"
gem.add_dependency "posix-spawn", "~> 0.3.6"
gem.platform = Gem::Platform.new("java")
end
if ENV["PLATFORM"] == "mingw32"
gem.add_dependency "win32console", "~> 1.3.0"
gem.platform = Gem::Platform.new("mingw32")
end
end end
+3 -14
View File
@@ -1,19 +1,8 @@
require "foreman/version"
module Foreman module Foreman
VERSION = "0.7.4"
class AppDoesNotExist < Exception; end class AppDoesNotExist < Exception; end
def self.runner
File.expand_path("../../bin/foreman-runner", __FILE__)
end
def self.jruby?
defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java"
end
def self.windows?
defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
end
end end
+31 -89
View File
@@ -1,96 +1,64 @@
require "foreman" require "foreman"
require "foreman/helpers"
require "foreman/engine" require "foreman/engine"
require "foreman/engine/cli"
require "foreman/export" require "foreman/export"
require "shellwords"
require "thor" require "thor"
class Foreman::CLI < Thor class Foreman::CLI < Thor
include Foreman::Helpers class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: ./Procfile"
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile" desc "start [PROCESS]", "Start the application, or a specific process"
class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
desc "start [PROCESS]", "Start the application (or a specific PROCESS)" method_option :port, :type => :numeric, :aliases => "-p"
method_option :concurrency, :type => :string, :aliases => "-c",
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env" :banner => '"alpha=5,bar=3"'
method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"'
method_option :port, :type => :numeric, :aliases => "-p"
class << self
# Hackery. Take the run method away from Thor so that we can redefine it.
def is_thor_reserved_word?(word, type)
return false if word == "run"
super
end
end
def start(process=nil) def start(process=nil)
check_procfile! check_procfile!
load_environment!
engine.load_procfile(procfile) if process
engine.options[:formation] = "#{process}=1" if process engine.execute(process, options)
engine.start else
engine.start(options)
end
end end
desc "export FORMAT LOCATION", "Export the application to another process management format" desc "export FORMAT LOCATION", "Export the application to another process management format"
method_option :app, :type => :string, :aliases => "-a" method_option :app, :type => :string, :aliases => "-a"
method_option :log, :type => :string, :aliases => "-l" method_option :log, :type => :string, :aliases => "-l"
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :port, :type => :numeric, :aliases => "-p" method_option :port, :type => :numeric, :aliases => "-p"
method_option :user, :type => :string, :aliases => "-u" method_option :user, :type => :string, :aliases => "-u"
method_option :template, :type => :string, :aliases => "-t" method_option :concurrency, :type => :string, :aliases => "-c",
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"' :banner => '"alpha=5,bar=3"'
def export(format, location=nil) def export(format, location=nil)
check_procfile! check_procfile!
load_environment!
engine.load_procfile(procfile) formatter = case format
formatter = Foreman::Export.formatter(format) when "upstart" then Foreman::Export::Upstart
formatter.new(location, engine, options).export when "inittab" then Foreman::Export::Inittab
else error "Unknown export format: #{format}."
end
formatter.new(engine).export(location, options)
rescue Foreman::Export::Exception => ex rescue Foreman::Export::Exception => ex
error ex.message error ex.message
end end
desc "check", "Validate your application's Procfile" private ######################################################################
def check def check_procfile!
check_procfile! error("Procfile does not exist.") unless File.exist?(procfile)
engine.load_procfile(procfile)
error "no processes defined" unless engine.processes.length > 0
puts "valid procfile detected (#{engine.process_names.join(', ')})"
end end
desc "run COMMAND [ARGS...]", "Run a command using your application's environment" def engine
@engine ||= Foreman::Engine.new(procfile)
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
def run(*args)
load_environment!
begin
exec engine.env, args.shelljoin
rescue Errno::EACCES
error "not executable: #{args.first}"
rescue Errno::ENOENT
error "command not found: #{args.first}"
end
end end
no_tasks do def procfile
def engine options[:procfile] || "./Procfile"
@engine ||= begin
engine_class = Foreman::Engine::CLI
engine = engine_class.new(
:formation => options[:formation],
:port => options[:port],
:root => options[:root]
)
engine
end
end
end end
private ###################################################################### private ######################################################################
@@ -100,34 +68,8 @@ private ######################################################################
exit 1 exit 1
end end
def check_procfile! def procfile_exists?(procfile)
error("#{procfile} does not exist.") unless File.exist?(procfile) File.exist?(procfile)
end
def load_environment!
if options[:env]
options[:env].split(",").each do |file|
engine.load_env file
end
else
default_env = File.join(engine.root, ".env")
engine.load_env default_env if File.exists?(default_env)
end
end
def procfile
case
when options[:procfile] then options[:procfile]
when options[:root] then File.expand_path(File.join(options[:app_root], "Procfile"))
else "Procfile"
end
end
def options
original_options = super
return original_options unless File.exists?(".foreman")
defaults = YAML::load_file(".foreman") || {}
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
end end
end end
-9
View File
@@ -1,9 +0,0 @@
module Foreman
module Distribution
def self.files
Dir[File.expand_path("../../../{bin,data,lib}/**/*", __FILE__)].select do |file|
File.file?(file)
end
end
end
end
+120 -243
View File
@@ -1,288 +1,165 @@
require "foreman" require "foreman"
require "foreman/env"
require "foreman/process" require "foreman/process"
require "foreman/procfile" require "foreman/utils"
require "pty"
require "tempfile" require "tempfile"
require "timeout" require "term/ansicolor"
require "fileutils" require "fileutils"
require "thread"
class Foreman::Engine class Foreman::Engine
attr_reader :env attr_reader :procfile
attr_reader :options attr_reader :directory
attr_reader :processes
# Create an +Engine+ for running processes extend Term::ANSIColor
#
# @param [Hash] options
#
# @option options [String] :formation (all=1) The process formation to use
# @option options [Fixnum] :port (5000) The base port to assign to processes
# @option options [String] :root (Dir.pwd) The root directory from which to run processes
#
def initialize(options={})
@options = options.dup
@options[:formation] ||= "all=1" COLORS = [ cyan, yellow, green, magenta, red ]
@env = {} def initialize(procfile)
@mutex = Mutex.new @procfile = read_procfile(procfile)
@names = {} @directory = File.expand_path(File.dirname(procfile))
@processes = []
@running = {}
@readers = {}
end end
# Start the processes registered to this +Engine+ def processes
# @processes ||= begin
def start procfile.split("\n").inject({}) do |hash, line|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully } next if line.strip == ""
trap("INT") { puts "SIGINT received"; terminate_gracefully } name, command = line.split(" ", 2)
trap("HUP") { puts "SIGHUP received"; terminate_gracefully } process = Foreman::Process.new(name, command)
process.color = next_color
startup hash.update(process.name => process)
spawn_processes
watch_for_output
sleep 0.1
watch_for_termination { terminate_gracefully }
shutdown
end
# Register a process to be run by this +Engine+
#
# @param [String] name A name for this process
# @param [String] command The command to run
# @param [Hash] options
#
# @option options [Hash] :env A custom environment for this process
#
def register(name, command, options={})
options[:env] ||= env
options[:cwd] ||= File.dirname(command.split(" ").first)
process = Foreman::Process.new(command, options)
@names[process] = name
@processes << process
end
# Clear the processes registered to this +Engine+
#
def clear
@names = {}
@processes = []
end
# Register processes by reading a Procfile
#
# @param [String] filename A Procfile from which to read processes to register
#
def load_procfile(filename)
options[:root] ||= File.dirname(filename)
Foreman::Procfile.new(filename).entries do |name, command|
register name, command, :cwd => options[:root]
end
self
end
# Load a .env file into the +env+ for this +Engine+
#
# @param [String] filename A .env file to load into the environment
#
def load_env(filename)
Foreman::Env.new(filename).entries do |name, value|
@env[name] = value
end
end
# Send a signal to all processesstarted by this +Engine+
#
# @param [String] signal The signal to send to each process
#
def killall(signal="SIGTERM")
@running.each do |pid, (process, index)|
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
begin
Process.kill(signal, -1 * pid)
rescue Errno::ESRCH, Errno::EPERM
end end
end end
end end
# Get the process formation def start(options={})
# proctitle "ruby: foreman master"
# @returns [Fixnum] The formation count for the specified process
# processes.each do |name, process|
def formation fork process, options
@formation ||= parse_formation(options[:formation]) end
trap("TERM") { kill_and_exit("TERM") }
trap("INT") { kill_and_exit("INT") }
watch_for_termination
end end
# List the available process names def execute(name, options={})
# fork processes[name], options
# @returns [Array] A list of process names
# trap("TERM") { kill_and_exit("TERM") }
def process_names trap("INT") { kill_and_exit("INT") }
@processes.map { |p| @names[p] }
watch_for_termination
end end
# Get the +Process+ for a specifid name def port_for(process, num, base_port=nil)
# base_port ||= 5000
# @param [String] name The process name offset = processes.keys.sort.index(process.name) * 100
# base_port.to_i + offset + num - 1
# @returns [Foreman::Process] The +Process+ for the specified name
#
def process(name)
@names.invert[name]
end end
# Yield each +Process+ in order private ######################################################################
#
def each_process def fork(process, options={})
process_names.each do |name| concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
yield name, process(name)
1.upto(concurrency[process.name]) do |num|
fork_individual(process, port_for(process, num, options[:port]))
end end
end end
# Get the root directory for this +Engine+ def fork_individual(process, port)
# ENV["PORT"] = port.to_s
# @returns [String] The root directory
#
def root
File.expand_path(options[:root] || Dir.pwd)
end
# Get the port for a given process and offset pid = Process.fork do
# run(process)
# @param [Foreman::Process] process A +Process+ associated with this engine
# @param [Fixnum] instance The instance of the process
#
# @returns [Fixnum] port The port to use for this instance of this process
#
def port_for(process, instance)
base_port + (@processes.index(process) * 100) + (instance - 1)
end
private
### Engine API ######################################################
def startup
raise TypeError, "must use a subclass of Foreman::Engine"
end
def output(name, data)
raise TypeError, "must use a subclass of Foreman::Engine"
end
def shutdown
raise TypeError, "must use a subclass of Foreman::Engine"
end
## Helpers ##########################################################
def base_port
(options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
end
def create_pipe
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
end
def name_for(pid)
process, index = @running[pid]
[ @names[process], index.to_s ].compact.join(".")
end
def parse_formation(formation)
pairs = @options[:formation].to_s.gsub(/\s/, "").split(",")
pairs.inject(Hash.new(0)) do |ax, pair|
process, amount = pair.split("=")
process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
ax
end end
info "started with pid #{pid}", process
running_processes[pid] = process
end end
def output_with_mutex(name, message) def run(process, log_to_file=true)
@mutex.synchronize do proctitle "ruby: foreman #{process.name}"
output name, message
end
end
def system(message) Dir.chdir directory do
output_with_mutex "system", message FileUtils.mkdir_p "log"
end command = process.command
def termination_message_for(status)
if status.exited?
"exited with code #{status.exitstatus}"
elsif status.signaled?
"terminated by SIG#{Signal.list.invert[status.termsig]}"
else
"died a mysterious death"
end
end
def flush_reader(reader)
until reader.eof?
data = reader.gets
output_with_mutex name_for(@readers.key(reader)), data
end
end
## Engine ###########################################################
def spawn_processes
@processes.each do |process|
1.upto(formation[@names[process]]) do |n|
reader, writer = create_pipe
begin
pid = process.run(:output => writer, :env => { "PORT" => port_for(process, n).to_s })
writer.puts "started with pid #{pid}"
rescue Errno::ENOENT
writer.puts "unknown command: #{process.command}"
end
@running[pid] = [process, n]
@readers[pid] = reader
end
end
end
def watch_for_output
Thread.new do
begin begin
loop do PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
(IO.select(@readers.values).first || []).each do |reader| until stdin.eof?
data = reader.gets info stdin.gets, process
output_with_mutex name_for(@readers.key(reader)), data
end end
end end
rescue Exception => ex rescue PTY::ChildExited, Interrupt
puts ex.message info "process exiting", process
puts ex.backtrace
end end
end end
end end
def kill_and_exit(signal="TERM")
info "terminating"
running_processes.each do |pid, process|
info "killing #{process.name} in pid #{pid}"
Process.kill(signal, pid)
end
exit 0
end
def info(message, process=nil)
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}:#{ENV["PORT"]}" : "system"
name.ljust(longest_process_name + 6) # add 6 for port padding
end
def print_info
info "currently running processes:"
running_processes.each do |pid, process|
info "pid #{pid}", process
end
end
def proctitle(title)
$0 = title
end
def read_procfile(procfile)
File.read(procfile)
end
def watch_for_termination def watch_for_termination
pid, status = Process.wait2 pid, status = Process.wait2
output_with_mutex name_for(pid), termination_message_for(status) process = running_processes.delete(pid)
@running.delete(pid) info "process terminated", process
yield if block_given? kill_and_exit
pid
rescue Errno::ECHILD
end end
def terminate_gracefully def running_processes
return if @terminating @running_processes ||= {}
@terminating = true end
system "sending SIGTERM to all processes"
killall "SIGTERM" def next_color
Timeout.timeout(5) do @current_color ||= -1
watch_for_termination while @running.length > 0 @current_color += 1
end @current_color >= COLORS.length ? "" : COLORS[@current_color]
rescue Timeout::Error
system "sending SIGKILL to all processes"
killall "SIGKILL"
end end
end end
-98
View File
@@ -1,98 +0,0 @@
require "foreman/engine"
class Foreman::Engine::CLI < Foreman::Engine
module Color
ANSI = {
:reset => 0,
:black => 30,
:red => 31,
:green => 32,
:yellow => 33,
:blue => 34,
:magenta => 35,
:cyan => 36,
:white => 37,
:bright_black => 30,
:bright_red => 31,
:bright_green => 32,
:bright_yellow => 33,
:bright_blue => 34,
:bright_magenta => 35,
:bright_cyan => 36,
:bright_white => 37,
}
def self.enable(io)
io.extend(self)
end
def color?
return false unless self.respond_to?(:isatty)
self.isatty && ENV["TERM"]
end
def color(name)
return "" unless color?
return "" unless ansi = ANSI[name.to_sym]
"\e[#{ansi}m"
end
end
FOREMAN_COLORS = %w( cyan yellow green magenta red blue intense_cyan intense_yellow
intense_green intense_magenta intense_red, intense_blue )
def startup
@colors = map_colors
proctitle "foreman: master"
end
def output(name, data)
data.to_s.chomp.split("\n").each do |message|
Color.enable($stdout) unless $stdout.respond_to?(:color?)
output = ""
output += $stdout.color(@colors[name.split(".").first].to_sym)
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
output += $stdout.color(:reset)
output += message
$stdout.puts output
end
end
def shutdown
end
private
def name_padding
@name_padding ||= begin
index_padding = @names.values.map { |n| formation[n] }.max.to_s.length + 1
name_padding = @names.values.map { |n| n.length + index_padding }.sort.last
[ 6, name_padding ].max
end
end
def pad_process_name(name)
name.ljust(name_padding, " ")
end
def map_colors
colors = Hash.new("white")
@names.values.each_with_index do |name, index|
colors[name] = FOREMAN_COLORS[index % FOREMAN_COLORS.length]
end
colors["system"] = "intense_white"
colors
end
def proctitle(title)
$0 = title
end
def termtitle(title)
printf("\033]0;#{title}\007") unless Foreman.windows?
end
end
-27
View File
@@ -1,27 +0,0 @@
require "foreman"
class Foreman::Env
attr_reader :entries
def initialize(filename)
@entries = File.read(filename).split("\n").inject({}) do |ax, line|
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
key = $1
case val = $2
when /\A'(.*)'\z/ then ax[key] = $1
when /\A"(.*)"\z/ then ax[key] = $1.gsub(/\\(.)/, '\1')
else ax[key] = val
end
end
ax
end
end
def entries
@entries.each do |key, value|
yield key, value
end
end
end
+1 -27
View File
@@ -1,34 +1,8 @@
require "foreman" require "foreman"
require "foreman/helpers"
module Foreman::Export module Foreman::Export
extend Foreman::Helpers
class Exception < ::Exception; end class Exception < ::Exception; end
def self.formatter(format)
begin
require "foreman/export/#{ format.tr('-', '_') }"
classy_format = classify(format)
formatter = constantize("Foreman::Export::#{ classy_format }")
rescue NameError => ex
error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })."
rescue LoadError => ex
error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')."
end
end
def self.error(message)
raise Foreman::Export::Exception.new(message)
end
end end
require "foreman/export/base"
require "foreman/export/inittab"
require "foreman/export/upstart" require "foreman/export/upstart"
require "foreman/export/bluepill" require "foreman/export/inittab"
require "foreman/export/runit"
require "foreman/export/supervisord"
require "foreman/export/launchd"
+6 -57
View File
@@ -1,37 +1,16 @@
require "foreman/export" require "foreman/export"
require "shellwords" require "foreman/utils"
class Foreman::Export::Base class Foreman::Export::Base
attr_reader :location
attr_reader :engine attr_reader :engine
attr_reader :options
attr_reader :formation
def initialize(location, engine, options={}) def initialize(engine)
@location = location @engine = engine
@engine = engine
@options = options.dup
@formation = engine.formation
end end
def export def export
error("Must specify a location") unless location raise "export method must be overridden"
FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
end
def app
options[:app] || "app"
end
def log
options[:log] || "/var/log/#{app}"
end
def user
options[:user] || app
end end
private ###################################################################### private ######################################################################
@@ -43,45 +22,15 @@ private ######################################################################
def say(message) def say(message)
puts "[foreman export] %s" % message puts "[foreman export] %s" % message
end end
def clean(filename)
return unless File.exists?(filename)
say "cleaning up: #{filename}"
FileUtils.rm(filename)
end
def shell_quote(value)
'"' + Shellwords.escape(value) + '"'
end
def export_template(name) def export_template(name)
name_without_first = name.split("/")[1..-1].join("/") File.read(File.expand_path("../../../../export/#{name}", __FILE__))
matchers = []
matchers << File.join(options[:template], name_without_first) if options[:template]
matchers << File.expand_path("~/.foreman/templates/#{name}")
matchers << File.expand_path("../../../../data/export/#{name}", __FILE__)
File.read(matchers.detect { |m| File.exists?(m) })
end
def write_template(name, target, binding)
compiled = ERB.new(export_template(name)).result(binding)
write_file target, compiled
end
def chmod(mode, file)
say "setting #{file} to mode #{mode}"
FileUtils.chmod mode, File.join(location, file)
end
def create_directory(dir)
say "creating: #{dir}"
FileUtils.mkdir_p(File.join(location, dir))
end end
def write_file(filename, contents) def write_file(filename, contents)
say "writing: #{filename}" say "writing: #{filename}"
File.open(File.join(location, filename), "w") do |file| File.open(filename, "w") do |file|
file.puts contents file.puts contents
end end
end end
-12
View File
@@ -1,12 +0,0 @@
require "erb"
require "foreman/export"
class Foreman::Export::Bluepill < Foreman::Export::Base
def export
super
clean "#{location}/#{app}.pill"
write_template "bluepill/master.pill.erb", "#{app}.pill", binding
end
end
+17 -12
View File
@@ -1,32 +1,37 @@
require "foreman/export" require "foreman/export/base"
class Foreman::Export::Inittab < Foreman::Export::Base class Foreman::Export::Inittab < Foreman::Export::Base
def export def export(fname=nil, options={})
error("Must specify a location") unless location app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
inittab = [] inittab = []
inittab << "# ----- foreman #{app} processes -----" inittab << "# ----- foreman #{app} processes -----"
index = 1 engine.processes.values.inject(1) do |index, process|
engine.each_process do |name, process| 1.upto(concurrency[process.name]) do |num|
1.upto(engine.formation[name]) do |num|
id = app.slice(0, 2).upcase + sprintf("%02d", index) id = app.slice(0, 2).upcase + sprintf("%02d", index)
port = engine.port_for(process, num) port = engine.port_for(process, num, options[:port])
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log}/#{name}-#{num}.log 2>&1'" inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
index += 1 index += 1
end end
index
end end
inittab << "# ----- end foreman #{app} processes -----" inittab << "# ----- end foreman #{app} processes -----"
inittab = inittab.join("\n") + "\n" inittab = inittab.join("\n") + "\n"
if location == "-" if fname
puts inittab FileUtils.mkdir_p(log_root)
FileUtils.chown(user, nil, log_root)
write_file(fname, inittab)
else else
say "writing: #{location}" puts inittab
File.open(location, "w") { |file| file.puts inittab }
end end
end end
-15
View File
@@ -1,15 +0,0 @@
require "erb"
require "foreman/export"
class Foreman::Export::Launchd < Foreman::Export::Base
def export
super
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
end
end
end
end
-34
View File
@@ -1,34 +0,0 @@
require "erb"
require "foreman/export"
class Foreman::Export::Runit < Foreman::Export::Base
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
def export
super
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
process_directory = "#{app}-#{name}-#{num}"
create_directory process_directory
create_directory "#{process_directory}/env"
create_directory "#{process_directory}/log"
write_template "runit/run.erb", "#{process_directory}/run", binding
chmod 0755, "#{process_directory}/run"
port = engine.port_for(process, num)
engine.env.merge("PORT" => port.to_s).each do |key, value|
write_file "#{process_directory}/env/#{key}", value
end
write_template "runit/log/run.erb", "#{process_directory}/log/run", binding
chmod 0755, "#{process_directory}/log/run"
end
end
end
end
-16
View File
@@ -1,16 +0,0 @@
require "erb"
require "foreman/export"
class Foreman::Export::Supervisord < Foreman::Export::Base
def export
super
Dir["#{location}/#{app}*.conf"].each do |file|
clean file
end
write_template "supervisord/app.conf.erb", "#{app}.conf", binding
end
end
+27 -11
View File
@@ -1,25 +1,41 @@
require "erb" require "erb"
require "foreman/export" require "foreman/export/base"
class Foreman::Export::Upstart < Foreman::Export::Base class Foreman::Export::Upstart < Foreman::Export::Base
def export def export(location, options={})
super error("Must specify a location") unless location
FileUtils.mkdir_p location
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
Dir["#{location}/#{app}*.conf"].each do |file| Dir["#{location}/#{app}*.conf"].each do |file|
clean file say "cleaning up: #{file}"
FileUtils.rm(file)
end end
write_template "upstart/master.conf.erb", "#{app}.conf", binding concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
engine.each_process do |name, process| master_template = export_template("upstart/master.conf.erb")
next if engine.formation[name] < 1 master_config = ERB.new(master_template).result(binding)
write_template "upstart/process_master.conf.erb", "#{app}-#{name}.conf", binding write_file "#{location}/#{app}.conf", master_config
1.upto(engine.formation[name]) do |num| process_template = export_template("upstart/process.conf.erb")
port = engine.port_for(process, num)
write_template "upstart/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding engine.processes.values.each do |process|
process_master_template = export_template("upstart/process_master.conf.erb")
process_master_config = ERB.new(process_master_template).result(binding)
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
1.upto(concurrency[process.name]) do |num|
port = engine.port_for(process, num, options[:port])
process_config = ERB.new(process_template).result(binding)
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
end end
end end
end end
end end
-45
View File
@@ -1,45 +0,0 @@
module Foreman::Helpers
# Copied whole sale from, https://github.com/defunkt/resque/
# Given a word with dashes, returns a camel cased version of it.
#
# classify('job-name') # => 'JobName'
def classify(dashed_word)
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
end # Tries to find a constant with the name specified in the argument string:
#
# constantize("Module") # => Module
# constantize("Test::Unit") # => Test::Unit
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
# account:
#
# C = 'outside'
# module M
# C = 'inside'
# C # => 'inside'
# constantize("C") # => 'outside', same as ::C
# end
#
# NameError is raised when the constant is unknown.
def constantize(camel_cased_word)
camel_cased_word = camel_cased_word.to_s
names = camel_cased_word.split('::')
names.shift if names.empty? || names.first.empty?
constant = Object
names.each do |name|
args = Module.method(:const_get).arity != 1 ? [false] : []
if constant.const_defined?(name, *args)
constant = constant.const_get(name)
else
constant = constant.const_missing(name)
end
end
constant
end
end
+4 -75
View File
@@ -1,85 +1,14 @@
require "foreman" require "foreman"
require "rubygems"
class Foreman::Process class Foreman::Process
attr_reader :name
attr_reader :command attr_reader :command
attr_reader :env attr_accessor :color
# Create a Process def initialize(name, command)
# @name = name
# @param [String] command The command to run
# @param [Hash] options
#
# @option options [String] :cwd (./) Change to this working directory before executing the process
# @option options [Hash] :env ({}) Environment variables to set for this process
#
def initialize(command, options={})
@command = command @command = command
@options = options.dup
@options[:env] ||= {}
end
# Run a +Process+
#
# @param [Hash] options
#
# @option options :env ({}) Environment variables to set for this execution
# @option options :output ($stdout) The output stream
#
# @returns [Fixnum] pid The +pid+ of the process
#
def run(options={})
env = options[:env] ? @options[:env].merge(options[:env]) : @options[:env]
output = options[:output] || $stdout
if Foreman.windows?
Dir.chdir(cwd) do
Process.spawn env, command, :out => output, :err => output, :new_pgroup => true
end
elsif Foreman.jruby?
Dir.chdir(cwd) do
require "posix/spawn"
POSIX::Spawn.spawn env, command, :out => output, :err => output, :pgroup => 0
end
else
Dir.chdir(cwd) do
Process.spawn env, command, :out => output, :err => output, :pgroup => 0
end
end
end
# Send a signal to this +Process+
#
# @param [String] signal The signal to send
#
def kill(signal)
pid && Process.kill(signal, -1 * pid)
rescue Errno::ESRCH
false
end
# Test whether or not this +Process+ is still running
#
# @returns [Boolean]
#
def alive?
kill(0)
end
# Test whether or not this +Process+ has terminated
#
# @returns [Boolean]
#
def dead?
!alive?
end
private
def cwd
@options[:cwd] || "."
end end
end end
-92
View File
@@ -1,92 +0,0 @@
require "foreman"
# Reads and writes Procfiles
#
# A valid Procfile entry is captured by this regex:
#
# /^([A-Za-z0-9_]+):\s*(.+)$/
#
# All other lines are ignored.
#
class Foreman::Procfile
# Initialize a Procfile
#
# @param [String] filename (nil) An optional filename to read from
#
def initialize(filename=nil)
@entries = []
load(filename) if filename
end
# Yield each +Procfile+ entry in order
#
def entries(&blk)
@entries.each do |(name, command)|
yield name, command
end
end
# Retrieve a +Procfile+ command by name
#
# @param [String] name The name of the Procfile entry to retrieve
#
def [](name)
@entries.detect { |n,c| name == n }.last
end
# Create a +Procfile+ entry
#
# @param [String] name The name of the +Procfile+ entry to create
# @param [String] command The command of the +Procfile+ entry to create
#
def []=(name, command)
delete name
@entries << [name, command]
end
# Remove a +Procfile+ entry
#
# @param [String] name The name of the +Procfile+ entry to remove
#
def delete(name)
@entries.reject! { |n,c| name == n }
end
# Load a Procfile from a file
#
# @param [String] filename The filename of the +Procfile+ to load
#
def load(filename)
@entries.replace parse(filename)
end
# Save a Procfile to a file
#
# @param [String] filename Save the +Procfile+ to this file
#
def save(filename)
File.open(filename, 'w') do |file|
file.puts self.to_s
end
end
# Get the +Procfile+ as a +String+
#
def to_s
@entries.map do |name, command|
[ name, command ].join(": ")
end.join("\n")
end
private
def parse(filename)
File.read(filename).split("\n").map do |line|
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
[$1, $2]
end
end.compact
end
end
+15
View File
@@ -0,0 +1,15 @@
require "foreman"
class Foreman::Utils
def self.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
end
-5
View File
@@ -1,5 +0,0 @@
module Foreman
VERSION = "0.47.0"
end
-244
View File
@@ -1,244 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "FOREMAN" "1" "April 2012" "Foreman 0.46.0" "Foreman Manual"
.
.SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications
.
.SH "SYNOPSIS"
\fBforeman start [process]\fR
.
.br
\fBforeman run <command>\fR
.
.br
\fBforeman export <format> [location]\fR
.
.SH "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\.
.
.SH "RUNNING"
\fBforeman start\fR is used to run your application directly from the command line\.
.
.P
If no additional parameters are passed, foreman will run one instance of each type of process defined in your Procfile\.
.
.P
If a parameter is passed, foreman will run one instance of the specified application type\.
.
.P
The following options control how the application is run:
.
.TP
\fB\-c\fR, \fB\-\-concurrency\fR
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
.
.TP
\fB\-e\fR, \fB\-\-env\fR
Specify one or more \.env files to load
.
.TP
\fB\-f\fR, \fB\-\-procfile\fR
Specify an alternate Procfile to load, implies \fB\-d\fR at the Procfile root\.
.
.TP
\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\.
.
.SH "EXPORTING"
\fBforeman export\fR is used to export your application to another process management format\.
.
.P
An location to export can be passed as an argument\. This argument may be either required or optional depending on the export format\.
.
.P
The following options control how the application is run:
.
.TP
\fB\-a\fR, \fB\-\-app\fR
Use this name rather than the application\'s root directory name as the name of the application when exporting\.
.
.TP
\fB\-c\fR, \fB\-\-concurrency\fR
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
.
.TP
\fB\-l\fR, \fB\-\-log\fR
Specify the directory to place process logs in\.
.
.TP
\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\-\-template\fR
Specify an alternate template to use for creating export files\. See \fIhttps://github\.com/ddollar/foreman/tree/master/data/export\fR for examples\.
.
.TP
\fB\-u\fR, \fB\-\-user\fR
Specify the user the application should be run as\. Defaults to the app name
.
.SH "GLOBAL OPTIONS"
These options control all modes of foreman\'s operation\.
.
.TP
\fB\-d\fR, \fB\-\-directory\fR
Specify an alternate application root\. This defaults to the directory containing the Procfile\.
.
.TP
\fB\-e\fR, \fB\-\-env\fR
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
.
.TP
\fB\-f\fR, \fB\-\-procfile\fR
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\.
.
.SH "EXPORT FORMATS"
foreman currently supports the following output formats:
.
.IP "\(bu" 4
bluepill
.
.IP "\(bu" 4
inittab
.
.IP "\(bu" 4
runit
.
.IP "\(bu" 4
upstart
.
.IP "" 0
.
.SH "INITTAB EXPORT"
Will export a chunk of inittab\-compatible configuration:
.
.IP "" 4
.
.nf
# \-\-\-\-\- foreman example processes \-\-\-\-\-
EX01:4:respawn:/bin/su \- example \-c \'PORT=5000 bundle exec thin start >> /var/log/web\-1\.log 2>&1\'
EX02:4:respawn:/bin/su \- example \-c \'PORT=5100 bundle exec rake jobs:work >> /var/log/job\-1\.log 2>&1\'
# \-\-\-\-\- end foreman example processes \-\-\-\-\-
.
.fi
.
.IP "" 0
.
.SH "UPSTART EXPORT"
Will create a series of upstart scripts in the location you specify\. Scripts will be structured to make the following commands valid:
.
.P
\fBstart appname\fR
.
.P
\fBstop appname\-processname\fR
.
.P
\fBrestart appname\-processname\-3\fR
.
.SH "PROCFILE"
A Procfile should contain both a name for the process and the command used to run it\.
.
.IP "" 4
.
.nf
web: bundle exec thin start
job: bundle exec rake jobs:work
.
.fi
.
.IP "" 0
.
.P
A process name may contain letters, numbers amd the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
.
.IP "" 4
.
.nf
$ foreman check
.
.fi
.
.IP "" 0
.
.SH "ENVIRONMENT"
If a \fB\.env\fR file exists in the current directory, the default environment will be read from it\. This file should contain key/value pairs, separated by \fB=\fR, with one key/value pair per line\.
.
.IP "" 4
.
.nf
FOO=bar
BAZ=qux
.
.fi
.
.IP "" 0
.
.SH "DEFAULT OPTIONS"
If a \fB\.foreman\fR file exists in the current directory, default options will be read from it\. This file should be in YAML format with the long option name as keys\. Example:
.
.IP "" 4
.
.nf
concurrency: alpha=0,bravo=1
port: 15000
.
.fi
.
.IP "" 0
.
.SH "EXAMPLES"
Start one instance of each process type, interleave the output on stdout:
.
.IP "" 4
.
.nf
$ foreman start
.
.fi
.
.IP "" 0
.
.P
Export the application in upstart format:
.
.IP "" 4
.
.nf
$ foreman export upstart /etc/init
.
.fi
.
.IP "" 0
.
.P
Run one process type from the application defined in a specific Procfile:
.
.IP "" 4
.
.nf
$ foreman start alpha \-p ~/myapp/Procfile
.
.fi
.
.IP "" 0
.
.SH "COPYRIGHT"
Foreman is Copyright (C) 2010 David Dollar \fIhttp://daviddollar\.org\fR
+5 -58
View File
@@ -4,12 +4,11 @@ foreman(1) -- manage Procfile-based applications
## SYNOPSIS ## SYNOPSIS
`foreman start [process]`<br> `foreman start [process]`<br>
`foreman run <command>`<br>
`foreman export <format> [location]` `foreman export <format> [location]`
## DESCRIPTION ## DESCRIPTION
Foreman is a manager for Procfile-based applications. Its aim is to **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 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 your application directly or export it to some other process management
format. format.
@@ -30,23 +29,10 @@ The following options control how the application is run:
Specify the number of each process type to run. The value passed in Specify the number of each process type to run. The value passed in
should be in the format `process=num,process=num` should be in the format `process=num,process=num`
* `-e`, `--env`:
Specify one or more .env files to load
* `-f`, `--procfile`:
Specify an alternate Procfile to load, implies `-d` at the Procfile root.
* `-p`, `--port`: * `-p`, `--port`:
Specify which port to use as the base for this application. Should be Specify which port to use as the base for this application. Should be
a multiple of 1000. 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.
## EXPORTING ## EXPORTING
`foreman export` is used to export your application to another process `foreman export` is used to export your application to another process
@@ -72,26 +58,14 @@ The following options control how the application is run:
Specify which port to use as the base for this application. Should be Specify which port to use as the base for this application. Should be
a multiple of 1000. a multiple of 1000.
* `-t`, `--template`:
Specify an alternate template to use for creating export files.
See <https://github.com/ddollar/foreman/tree/master/data/export> for examples.
* `-u`, `--user`: * `-u`, `--user`:
Specify the user the application should be run as. Defaults to the Specify the user the application should be run as. Defaults to the
app name app name
## GLOBAL OPTIONS ## OPTIONS
These options control all modes of foreman's operation. These options control all modes of foreman's operation.
* `-d`, `--directory`:
Specify an alternate application root. This defaults to the directory
containing the Procfile.
* `-e`, `--env`:
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
containing directory will be assumed to be the root directory of the containing directory will be assumed to be the root directory of the
@@ -101,12 +75,8 @@ These options control all modes of foreman's operation.
foreman currently supports the following output formats: foreman currently supports the following output formats:
* bluepill
* inittab * inittab
* runit
* upstart * upstart
## INITTAB EXPORT ## INITTAB EXPORT
@@ -134,31 +104,8 @@ will be structured to make the following commands valid:
A Procfile should contain both a name for the process and the command used A Procfile should contain both a name for the process and the command used
to run it. to run it.
web: bundle exec thin start web bundle exec thin start
job: bundle exec rake jobs:work job bundle exec rake jobs:work
A process name may contain letters, numbers amd the underscore character.
You can validate your Procfile format using the `check` command:
$ foreman check
## ENVIRONMENT
If a `.env` file exists in the current directory, the default environment will
be read from it. This file should contain key/value pairs, separated by `=`, with
one key/value pair per line.
FOO=bar
BAZ=qux
## DEFAULT OPTIONS
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
name as keys. Example:
concurrency: alpha=0,bravo=1
port: 15000
## EXAMPLES ## EXAMPLES
@@ -172,7 +119,7 @@ Export the application in upstart format:
Run one process type from the application defined in a specific Procfile: Run one process type from the application defined in a specific Procfile:
$ foreman start alpha -p ~/myapp/Procfile $ foreman start alpha -p ~/app/Procfile
## COPYRIGHT ## COPYRIGHT
+34 -50
View File
@@ -1,25 +1,12 @@
require "spec_helper" require "spec_helper"
require "foreman/cli" require "foreman/cli"
describe "Foreman::CLI", :fakefs do describe "Foreman::CLI" do
subject { Foreman::CLI.new } subject { Foreman::CLI.new }
describe ".foreman" do
before { File.open(".foreman", "w") { |f| f.puts "formation: alpha=2" } }
it "provides default options" do
subject.send(:options)["formation"].should == "alpha=2"
end
it "is overridden by options at the cli" do
subject = Foreman::CLI.new([], :formation => "alpha=3")
subject.send(:options)["formation"].should == "alpha=3"
end
end
describe "start" do describe "start" do
describe "when a Procfile doesnt exist", :fakefs do describe "with a non-existent Procfile" do
it "displays 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
subject.start subject.start
@@ -27,50 +14,47 @@ describe "Foreman::CLI", :fakefs do
end end
end end
describe "with a valid Procfile" do describe "with a Procfile" do
it "can run a single command" do before(:each) { write_procfile }
without_fakefs do
output = foreman("start env -f #{resource_path("Procfile")}")
output.should =~ /env.1/
output.should_not =~ /test.1/
end
end
it "can run all commands" do it "runs successfully" do
without_fakefs do dont_allow(subject).error
output = foreman("start -f #{resource_path("Procfile")} -e #{resource_path(".env")}") mock.instance_of(Foreman::Engine).start({})
output.should =~ /echo.1 \| echoing/ subject.start
output.should =~ /env.1 \| bar/
output.should =~ /test.1 \| testing/
end
end end
end end
end end
describe "check" do describe "export" do
it "with a valid Procfile displays the jobs" do describe "with a non-existent Procfile" do
write_procfile it "prints an error" do
foreman("check").should == "valid procfile detected (alpha, bravo)\n" mock_error(subject, "Procfile does not exist.") do
dont_allow.instance_of(Foreman::Engine).export
subject.export("testapp")
end
end
end end
it "with a blank Procfile displays an error" do describe "with a Procfile" do
FileUtils.touch "Procfile" before(:each) { write_procfile }
foreman("check").should == "ERROR: no processes defined\n"
end
it "without a Procfile displays an error" do describe "with an invalid formatter" do
FileUtils.rm_f "Procfile" it "prints an error" do
foreman("check").should == "ERROR: Procfile does not exist.\n" mock_error(subject, "Unknown export format: invalidformatter.") do
end subject.export("invalidformatter")
end end
end
end
describe "run" do describe "with a valid config" do
it "can run a command" do before(:each) { write_foreman_config("testapp") }
forked_foreman("run echo 1").should == "1\n"
end
it "includes the environment" do it "runs successfully" do
forked_foreman("run #{resource_path("bin/env FOO")} -e #{resource_path(".env")}").should == "bar\n" dont_allow(subject).error
mock.instance_of(Foreman::Export::Upstart).export("/tmp/foo", {})
subject.export("upstart", "/tmp/foo")
end
end
end end
end end
+25 -79
View File
@@ -1,104 +1,50 @@
require "spec_helper" require "spec_helper"
require "foreman/engine" require "foreman/engine"
class Foreman::Engine::Tester < Foreman::Engine describe "Foreman::Engine" do
attr_reader :buffer subject { Foreman::Engine.new("Procfile") }
def startup
@buffer = ""
end
def output(name, data)
@buffer += "#{name}: #{data}"
end
def shutdown
end
end
describe "Foreman::Engine", :fakefs do
subject do
write_procfile "Procfile"
Foreman::Engine::Tester.new.load_procfile("Procfile")
end
describe "initialize" do describe "initialize" do
describe "with a Procfile" do describe "without an existing Procfile" do
before { write_procfile } it "raises an error" do
lambda { subject }.should raise_error
end
end
describe "with a Procfile" do
it "reads the processes" do it "reads the processes" do
subject.process("alpha").command.should == "./alpha" write_procfile
subject.process("bravo").command.should == "./bravo" subject.processes["alpha"].command.should == "./alpha"
subject.processes["bravo"].command.should == "./bravo"
end end
end end
end end
describe "start" do describe "start" do
it "forks the processes" do it "forks the processes" do
mock(subject.process("alpha")).run(anything) write_procfile
mock(subject.process("bravo")).run(anything) mock(subject).fork(subject.processes["alpha"], {})
mock(subject).watch_for_output mock(subject).fork(subject.processes["bravo"], {})
mock(subject).watch_for_termination mock(subject).watch_for_termination
subject.start subject.start
end end
it "handles concurrency" do it "handles concurrency" do
subject.options[:formation] = "alpha=2" write_procfile
mock(subject.process("alpha")).run(anything).twice mock(subject).fork_individual(subject.processes["alpha"], 5000)
mock(subject.process("bravo")).run(anything).never mock(subject).fork_individual(subject.processes["alpha"], 5001)
mock(subject).watch_for_output mock(subject).fork_individual(subject.processes["bravo"], 5100)
mock(subject).watch_for_termination mock(subject).watch_for_termination
subject.start subject.start(:concurrency => "alpha=2")
end end
end end
describe "directories" do describe "execute" do
it "has the directory default relative to the Procfile" do it "runs the processes" do
write_procfile "/some/app/Procfile" write_procfile
engine = Foreman::Engine.new.load_procfile("/some/app/Procfile") mock(subject).fork(subject.processes["alpha"], {})
engine.root.should == "/some/app" mock(subject).watch_for_termination
subject.execute("alpha")
end end
end end
describe "environment" do
it "should read env files" do
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
subject.load_env("/tmp/env")
subject.env["FOO"].should == "baz"
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") }
subject.load_env "/tmp/env1"
subject.load_env "/tmp/env2"
subject.env["FOO"].should == "bar"
subject.env["BAZ"].should == "qux"
end
it "should handle quoted values" do
File.open("/tmp/env", "w") do |f|
f.puts 'FOO=bar'
f.puts 'BAZ="qux"'
f.puts "FRED='barney'"
f.puts 'OTHER="escaped\"quote"'
end
subject.load_env "/tmp/env"
subject.env["FOO"].should == "bar"
subject.env["BAZ"].should == "qux"
subject.env["FRED"].should == "barney"
subject.env["OTHER"].should == 'escaped"quote'
end
it "should fail if specified and doesnt exist" do
lambda { subject.load_env "/tmp/env" }.should raise_error(Errno::ENOENT)
end
it "should set port from .env if specified" do
File.open("/tmp/env", "w") { |f| f.puts("PORT=9000") }
subject.load_env "/tmp/env"
subject.send(:base_port).should == 9000
end
end
end end
-19
View File
@@ -1,19 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export"
describe "Foreman::Export::Base", :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:location) { "/tmp/init" }
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
let(:subject) { Foreman::Export::Base.new(location, engine) }
it "has a say method for displaying info" do
mock(subject).puts("[foreman export] foo")
subject.send(:say, "foo")
end
it "raises errors as a Foreman::Export::Exception" do
lambda { subject.send(:error, "foo") }.should raise_error(Foreman::Export::Exception, "foo")
end
end
-37
View File
@@ -1,37 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/bluepill"
require "tmpdir"
describe Foreman::Export::Bluepill, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:bluepill) { Foreman::Export::Bluepill.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("bluepill") }
before(:each) { stub(bluepill).say }
it "exports to the filesystem" do
bluepill.export
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app.pill"))
end
it "cleans up if exporting into an existing dir" do
mock(FileUtils).rm("/tmp/init/app.pill")
bluepill.export
bluepill.export
end
context "with a process formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
bluepill.export
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app-concurrency.pill"))
end
end
end
-40
View File
@@ -1,40 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/inittab"
require "tmpdir"
describe Foreman::Export::Inittab, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:location) { "/tmp/inittab" }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:inittab) { Foreman::Export::Inittab.new(location, engine, options) }
before(:each) { load_export_templates_into_fakefs("inittab") }
before(:each) { stub(inittab).say }
it "exports to the filesystem" do
inittab.export
File.read("/tmp/inittab").should == example_export_file("inittab/inittab.default")
end
context "to stdout" do
let(:location) { "-" }
it "exports to stdout" do
mock(inittab).puts example_export_file("inittab/inittab.default")
inittab.export
end
end
context "with concurrency" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
inittab.export
File.read("/tmp/inittab").should == example_export_file("inittab/inittab.concurrency")
end
end
end
-21
View File
@@ -1,21 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/launchd"
require "tmpdir"
describe Foreman::Export::Launchd, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:options) { Hash.new }
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
let(:launchd) { Foreman::Export::Launchd.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("launchd") }
before(:each) { stub(launchd).say }
it "exports to the filesystem" do
launchd.export
File.read("/tmp/init/app-alpha-1.plist").should == example_export_file("launchd/launchd-a.default")
File.read("/tmp/init/app-bravo-1.plist").should == example_export_file("launchd/launchd-b.default")
end
end
-36
View File
@@ -1,36 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/runit"
require "tmpdir"
describe Foreman::Export::Runit, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
let(:engine) { Foreman::Engine.new(:formation => "alpha=2,bravo=1").load_procfile(procfile) }
let(:options) { Hash.new }
let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, options) }
before(:each) { load_export_templates_into_fakefs("runit") }
before(:each) { stub(runit).say }
before(:each) { stub(FakeFS::FileUtils).chmod }
it "exports to the filesystem" do
engine.env["BAR"] = "baz"
runit.export
File.read("/tmp/init/app-alpha-1/run").should == example_export_file('runit/app-alpha-1/run')
File.read("/tmp/init/app-alpha-1/log/run").should == example_export_file('runit/app-alpha-1/log/run')
File.read("/tmp/init/app-alpha-1/env/PORT").should == "5000\n"
File.read("/tmp/init/app-alpha-1/env/BAR").should == "baz\n"
File.read("/tmp/init/app-alpha-2/run").should == example_export_file('runit/app-alpha-2/run')
File.read("/tmp/init/app-alpha-2/log/run").should == example_export_file('runit/app-alpha-2/log/run')
File.read("/tmp/init/app-alpha-2/env/PORT").should == "5001\n"
File.read("/tmp/init/app-alpha-2/env/BAR").should == "baz\n"
File.read("/tmp/init/app-bravo-1/run").should == example_export_file('runit/app-bravo-1/run')
File.read("/tmp/init/app-bravo-1/log/run").should == example_export_file('runit/app-bravo-1/log/run')
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
end
it "creates a full path to the export directory" do
expect { runit.export }.to_not raise_error(Errno::ENOENT)
end
end
-36
View File
@@ -1,36 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/supervisord"
require "tmpdir"
describe Foreman::Export::Supervisord, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:supervisord) { Foreman::Export::Supervisord.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("supervisord") }
before(:each) { stub(supervisord).say }
it "exports to the filesystem" do
supervisord.export
File.read("/tmp/init/app.conf").should == example_export_file("supervisord/app-alpha-1.conf")
end
it "cleans up if exporting into an existing dir" do
mock(FileUtils).rm("/tmp/init/app.conf")
supervisord.export
supervisord.export
end
context "with concurrency" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
supervisord.export
File.read("/tmp/init/app.conf").should == example_export_file("supervisord/app-alpha-2.conf")
end
end
end
-86
View File
@@ -1,88 +1,2 @@
require "spec_helper" require "spec_helper"
require "foreman/engine"
require "foreman/export/upstart" require "foreman/export/upstart"
require "tmpdir"
describe Foreman::Export::Upstart, :fakefs do
let(:procfile) { write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("upstart") }
before(:each) { stub(upstart).say }
it "exports to the filesystem" do
upstart.export
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
File.read("/tmp/init/app-bravo.conf").should == example_export_file("upstart/app-bravo.conf")
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf")
end
it "cleans up if exporting into an existing dir" do
mock(FileUtils).rm("/tmp/init/app.conf")
mock(FileUtils).rm("/tmp/init/app-alpha.conf")
mock(FileUtils).rm("/tmp/init/app-alpha-1.conf")
mock(FileUtils).rm("/tmp/init/app-bravo.conf")
mock(FileUtils).rm("/tmp/init/app-bravo-1.conf")
upstart.export
upstart.export
end
it "quotes and escapes environment variables" do
engine.env['KEY'] = 'd"\|d'
upstart.export
"foobarfoo".should include "bar"
File.read("/tmp/init/app-alpha-1.conf").should =~ /KEY="d\\"\\\\\\\|d/
end
context "with a formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
upstart.export
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("upstart/app-alpha-2.conf")
File.exists?("/tmp/init/app-bravo-1.conf").should == false
end
end
context "with alternate templates" do
let(:template) { "/tmp/alternate" }
let(:options) { { :app => "app", :template => template } }
before do
FileUtils.mkdir_p template
File.open("#{template}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
end
it "can export with alternate template files" do
upstart.export
File.read("/tmp/init/app.conf").should == "alternate_template\n"
end
end
context "with alternate templates from home dir" do
before do
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/upstart")
File.open(File.expand_path("~/.foreman/templates/upstart/master.conf.erb"), "w") do |file|
file.puts "default_alternate_template"
end
end
it "can export with alternate template files" do
upstart.export
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
end
end
end
-22
View File
@@ -1,24 +1,2 @@
require "spec_helper" require "spec_helper"
require "foreman/export" require "foreman/export"
describe "Foreman::Export" do
subject { Foreman::Export }
describe "with a formatter that doesn't declare the appropriate class" do
it "prints an error" do
mock(subject).require("foreman/export/invalidformatter")
mock_export_error("Unknown export format: invalidformatter (no class Foreman::Export::Invalidformatter).") do
subject.formatter("invalidformatter")
end
end
end
describe "with an invalid formatter" do
it "prints an error" do
mock_export_error("Unknown export format: invalidformatter (unable to load file 'foreman/export/invalidformatter').") do
subject.formatter("invalidformatter")
end
end
end
end
-26
View File
@@ -1,26 +0,0 @@
require "spec_helper"
require "foreman/helpers"
describe "Foreman::Helpers" do
before do
module Foo
class Bar; end
end
end
after do
Object.send(:remove_const, :Foo)
end
subject { o = Object.new; o.extend(Foreman::Helpers); o }
it "should classify words" do
subject.classify("foo").should == "Foo"
subject.classify("foo-bar").should == "FooBar"
end
it "should constantize words" do
subject.constantize("Object").should == Object
subject.constantize("Foo::Bar").should == Foo::Bar
end
end
+2 -48
View File
@@ -1,48 +1,2 @@
require 'spec_helper' require "spec_helper"
require 'foreman/process' require "foreman/process"
require 'ostruct'
require 'timeout'
require 'tmpdir'
describe Foreman::Process do
def run(process, options={})
rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
process.run(options.merge(:output => wr))
rd.gets
end
describe "#run" do
it "runs the process" do
process = Foreman::Process.new(resource_path("bin/test"))
run(process).should == "testing\n"
end
it "can set environment" do
process = Foreman::Process.new(resource_path("bin/env FOO"), :env => { "FOO" => "bar" })
run(process).should == "bar\n"
end
it "can set per-run environment" do
process = Foreman::Process.new(resource_path("bin/env FOO"))
run(process, :env => { "FOO" => "bar "}).should == "bar\n"
end
it "can handle env vars in the command" do
process = Foreman::Process.new(resource_path("bin/echo $FOO"), :env => { "FOO" => "bar" })
run(process).should == "bar\n"
end
it "can handle per-run env vars in the command" do
process = Foreman::Process.new(resource_path("bin/echo $FOO"))
run(process, :env => { "FOO" => "bar" }).should == "bar\n"
end
it "should output utf8 properly" do
process = Foreman::Process.new(resource_path("bin/utf8"))
run(process).should == "\xFF\x03\n"
end
end
end
-41
View File
@@ -1,41 +0,0 @@
require 'spec_helper'
require 'foreman/procfile'
require 'pathname'
require 'tmpdir'
describe Foreman::Procfile, :fakefs do
subject { Foreman::Procfile.new }
it "can load from a file" do
write_procfile
subject.load "Procfile"
subject["alpha"].should == "./alpha"
subject["bravo"].should == "./bravo"
end
it "loads a passed-in Procfile" do
write_procfile
procfile = Foreman::Procfile.new("Procfile")
procfile["alpha"].should == "./alpha"
procfile["bravo"].should == "./bravo"
end
it "can have a process appended to it" do
subject["charlie"] = "./charlie"
subject["charlie"].should == "./charlie"
end
it "can write to a string" do
subject["foo"] = "./foo"
subject["bar"] = "./bar"
subject.to_s.should == "foo: ./foo\nbar: ./bar"
end
it "can write to a file" do
subject["foo"] = "./foo"
subject["bar"] = "./bar"
subject.save "/tmp/proc"
File.read("/tmp/proc").should == "foo: ./foo\nbar: ./bar\n"
end
end
-5
View File
@@ -8,9 +8,4 @@ describe Foreman do
it { should be_a String } it { should be_a String }
end end
describe "runner" do
it "should exist" do
File.exists?(Foreman.runner).should == true
end
end
end end
-18
View File
@@ -1,18 +0,0 @@
require "spec_helper"
describe "spec helpers" do
describe "#preserving_env" do
after { ENV.delete "FOO" }
it "should remove added environment vars" do
preserving_env { ENV["FOO"] = "baz" }
ENV["FOO"].should == nil
end
it "should reset modified environment vars" do
ENV["FOO"] = "bar"
preserving_env { ENV["FOO"] = "baz"}
ENV["FOO"].should == "bar"
end
end
end
-1
View File
@@ -1 +0,0 @@
FOO=bar
-4
View File
@@ -1,4 +0,0 @@
echo: bin/echo echoing
env: bin/env FOO
test: bin/test
utf8: bin/utf8
-2
View File
@@ -1,2 +0,0 @@
#!/bin/sh
echo $*
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
echo ${!1}
-2
View File
@@ -1,2 +0,0 @@
#!/bin/sh
echo "testing"
-2
View File
@@ -1,2 +0,0 @@
#!/usr/bin/env ruby
puts "\xff\x03"
@@ -1,49 +0,0 @@
Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "app"
app.gid = "app"
app.process("alpha-1") do |process|
process.start_command = "./alpha"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5000"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-alpha"
end
app.process("alpha-2") do |process|
process.start_command = "./alpha"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5001"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-alpha"
end
end
-46
View File
@@ -1,46 +0,0 @@
Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "app"
app.gid = "app"
app.process("alpha-1") do |process|
process.start_command = "./alpha"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5000"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-alpha"
end
app.process("bravo-1") do |process|
process.start_command = "./bravo"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5100"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-bravo"
end
end
@@ -1,4 +0,0 @@
# ----- foreman app processes -----
AP01:4:respawn:/bin/su - app -c 'PORT=5000 ./alpha >> /var/log/app/alpha-1.log 2>&1'
AP02:4:respawn:/bin/su - app -c 'PORT=5001 ./alpha >> /var/log/app/alpha-2.log 2>&1'
# ----- end foreman app processes -----
@@ -1,4 +0,0 @@
# ----- foreman app processes -----
AP01:4:respawn:/bin/su - app -c 'PORT=5000 ./alpha >> /var/log/app/alpha-1.log 2>&1'
AP02:4:respawn:/bin/su - app -c 'PORT=5100 ./bravo >> /var/log/app/bravo-1.log 2>&1'
# ----- end foreman app processes -----
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>app-alpha-1</string>
<key>ProgramArguments</key>
<array>
<string>./alpha</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/var/log/app/app-alpha-1.log</string>
<key>UserName</key>
<string>app</string>
<key>WorkingDirectory</key>
<string>/tmp/app</string>
</dict>
</plist>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>app-bravo-1</string>
<key>ProgramArguments</key>
<array>
<string>./bravo</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/var/log/app/app-bravo-1.log</string>
<key>UserName</key>
<string>app</string>
<key>WorkingDirectory</key>
<string>/tmp/app</string>
</dict>
</plist>
@@ -1,7 +0,0 @@
#!/bin/sh
set -e
LOG=/var/log/app/alpha-1
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
@@ -1,3 +0,0 @@
#!/bin/sh
cd /tmp/app
exec chpst -u app -e /tmp/init/app-alpha-1/env ./alpha bar=baz
@@ -1,7 +0,0 @@
#!/bin/sh
set -e
LOG=/var/log/app/alpha-2
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
@@ -1,3 +0,0 @@
#!/bin/sh
cd /tmp/app
exec chpst -u app -e /tmp/init/app-alpha-2/env ./alpha bar=baz
@@ -1,7 +0,0 @@
#!/bin/sh
set -e
LOG=/var/log/app/bravo-1
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
@@ -1,3 +0,0 @@
#!/bin/sh
cd /tmp/app
exec chpst -u app -e /tmp/init/app-bravo-1/env ./bravo

Some files were not shown because too many files have changed in this diff Show More