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
136 changed files with 430 additions and 4288 deletions

17
.gitignore vendored
View File

@@ -1,10 +1,7 @@
/.bundle
/.rbenv-version
/.yardoc
/coverage
/example/log/*
/man/*.html
/man/*.markdown
/pkg
/tags
/vendor
.bundle
coverage
example/log/*
man/*.?
man/*.html
man/*.markdown
pkg

View File

@@ -1,21 +0,0 @@
script: bundle exec rake spec
matrix:
allow_failures:
- rvm: jruby
notifications:
email: false
webhooks:
on_success: always
on_failure: always
urls:
- http://dx-helper.herokuapp.com/travis
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.0
- jruby

View File

@@ -1,354 +0,0 @@
## 0.63.0 (2013-04-15)
* Revert "Ensure foreman is the process group leader" [John Griffin]
* remove posix-spawn dependency as it does not work in jruby 1.7.3 [Andrew Brown & Corey Downing]
* Replace Foreman::Env with dotenv [Brandon Keepers]
* [foreman-runner] fix sourcing as . is rarely in PATH [Barry Allard]
* Fixed specs to pass. [Kentaro Kuribayashi]
* Permit underscore for command name in Procfile. [Kentaro Kuribayashi]
* Update man/foreman.1 [Patrick Ellis]
* Remove tmux option from man page [Donald Plummer]
* Prevent upstart export from deleting similarly named upstart files [Andy Morris]
* Add MIT license text [Per Andersson]
* use "start|stop\ on runlevel [x]" for upstart config [Nick Messick]
## 0.62.0 (2013-03-08)
* Merge pull request #334 from ged/reentrant_signal_handlers [David Dollar]
* Merge pull request #335 from ged/20_encoding_fix [David Dollar]
* Try to allow children to shut down gracefully [Michael Granger]
* Add deferred signal-handling (fixes #332). [Michael Granger]
* Fix spec encoding problem under Ruby 2.0.0. [Michael Granger]
* add ruby 2.0 to travis [David Dollar]
* Merge pull request #327 from patheticpat/master [David Dollar]
* Fixed a typo in cli options description [Michael Kaiser]
* handled by mingw now [David Dollar]
## 0.61.0 (2013-01-14)
* Fix bug in color definitons [nseo]
* Fix for high CPU load when processes close output [Pavel Forkert]
* Ensure foreman is the process group leader [Christos Trochalakis]
* Don't ignore blank lines in the output [Matt Venables]
* Add license to gemspec [petedmarsh]
* Since JRuby 1.9 doesn't require posix/spawn, only follow that path if JRuby is loaded and running in 1.8 mode. [Adam Hutchison]
* Remove explicit requirement on rubygems. [Cyril Rohr]
* Dont use shared_path variable before multistage has a chance at it [Aditya Sanghi]
* Strip Windows Line Endings [Paul Morton]
* Fix man page: --directory is actually --root. [Evan Jones]
* Add timeout switch to CLI [Paulo Luis Franchini Casaretto]
* Remove expectation of double quotes around environment variables from test comparisons [Kevin McAllister]
* Remove explicit wrapping of Shellwords.escape in double quotes [Kevin McAllister]
## 0.60.2 (2012-10-08)
* Fix for nil value on io select loop, fixes #260 [Silvio Relli]
## 0.60.1 (2012-10-08)
* sleep on select() to avoid spinning the cpu [Silvio Relli]
## 0.60.0 (2012-09-25)
* foreman run can run things from the Procfile like heroku run. [Dan Peterson]
## 0.59.0 (2012-09-15)
* Use /bin/sh instead of bash for foreman-runner [Jeremy Evans]
## 0.58.0 (2012-09-14)
* dont set HOME [David Dollar]
* Add StandardOutPath to launchd export [Aaron Kalin]
* Add command argument string splitting [Aaron Kalin]
* Cleanup launchd exporter [Aaron Kalin]
* Enable trim_mode via '-' in ERB templates [Aaron Kalin]
* Add support for setting environment variables [Aaron Kalin]
* foreman run should exit with the same code as its command [Omar Khan]
* Handle multiline strings in .env file [Szymon Nowak]
* Use path and env variables in the inittab export [Indrek Juhkam]
* fixed the directory option [Arnaud Lachaume]
* Add capistrano export support [Daniel Farrell]
## 0.57.0 (2012-08-21)
* fix startup checks for upstart exporter [Aditya Sanghi]
## 0.56.0 (2012-08-19)
* read .profile, not .profile.d [David Dollar]
## 0.55.0 (2012-08-14)
* use a forked process to exec a run with environment [David Dollar]
## 0.54.0 (2012-08-14)
* use Foreman::Process to extract command running [David Dollar]
* changed to check env for bash [brntbeer]
## 0.53.0 (2012-07-24)
* put app root in $HOME [David Dollar]
## 0.52.0 (2012-07-24)
* wrap command in a runner that sources .profile.d scripts [David Dollar]
* fix upstart export specs [David Dollar]
* Make upstart export start/stop with network [Daniel Farrell]
## 0.51.0 (2012-07-11)
* dont try to colorize windows [David Dollar]
## 0.50.0 (2012-07-11)
* handle windows [David Dollar]
## 0.49.0 (2012-07-11)
* 1.8 compatibility [David Dollar]
* use one pgroup for all of foreman and kill that since ruby 1.8 sucks at pgroups [David Dollar]
* better debugging [David Dollar]
## 0.48.0 (2012-07-10)
* allow old exporter format to work, but with deprecation warning [David Dollar]
* remove debugging code [David Dollar]
* Merge pull request #219 from MarkDBlackwell/patch-1 [David Dollar]
* Avoid crash by verifying the existence of SIGHUP before accessing it. [Mark D. Blackwell]
* allow color to be forced on [David Dollar]
* terminate gracefully if stdout goes away [David Dollar]
* always flush output [David Dollar]
* Merge pull request #212 from morgoth/added-version-command [David Dollar]
* added command for displaying foreman version [Wojciech Wnętrzak]
* Merge pull request #211 from morgoth/fixed-yaml-usage [David Dollar]
* fixed using YAML [Wojciech Wnętrzak]
* test on more things, but don't fail [David Dollar]
* changelog [David Dollar]
* 0.48.0.pre1 [David Dollar]
* foreman doesn't work on ruby 1.8, may try to fix later [David Dollar]
* use bash [David Dollar]
* massive refactoring for programmatic control and stability [David Dollar]
* Merge pull request #164 from hsume2/master [David Dollar]
* Only run tmux specs if tmux is installed [Henry Hsu]
* Do not assume BUNDLE_GEMFILE [Henry Hsu]
* Add support for starting procfile in tmux session [Henry Hsu]
## 0.47.0 (2012-06-07)
* Fix multi-word argument handling in `foreman run`. [Daniel Brockman]
* Make 'PORT=5000 foreman start' work [Koen Van der Auwera]
* Terminate gracefully upon SIGHUP [Stefan Schüßler]
* Set port from .env if specified [Koen Van der Auwera]
* Updated bluepill exporter to use environment variables from .env [Aneeth]
* Added launchd exporter [Maxwell Swadling]
* Quote and escape environment variables in upstart templates [Matt Griffin]
* Added list of ports to other languages to README [elf Pavlik]
## 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]

31
Gemfile
View File

@@ -1,25 +1,16 @@
source "http://rubygems.org"
gemspec
platform :mingw do
gem "win32console", "~> 1.3.0"
end
platform :jruby, :ruby_18 do
gem "posix-spawn", "~> 0.3.6"
end
group :development do
gem 'aws-s3'
gem 'parka'
gem 'rake'
gem 'ronn'
gem 'fakefs', '~> 0.3.2'
gem 'rr', '~> 1.0.2'
gem 'rspec', '~> 2.0'
gem "simplecov", :require => false
gem 'timecop', '0.6.1'
gem 'yard'
gem 'mime-types', '~> 1.25.1'
gem 'rdiscount', '~> 1.6.8'
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'

View File

@@ -1,71 +1,37 @@
PATH
remote: .
specs:
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
GEM
remote: http://rubygems.org/
specs:
aws-s3 (0.6.3)
builder
mime-types
xml-simple
builder (3.2.2)
diff-lcs (1.2.5)
docile (1.1.3)
dotenv (0.10.0)
fakefs (0.3.2)
hpricot (0.8.6)
hpricot (0.8.6-java)
mime-types (1.25.1)
multi_json (1.9.0)
mustache (0.99.5)
posix-spawn (0.3.8)
rake (10.1.1)
rdiscount (1.6.8)
ronn (0.7.3)
hpricot (>= 0.8.2)
mustache (>= 0.7.0)
rdiscount (>= 1.5.8)
rr (1.0.5)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.8)
rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6)
simplecov (0.8.2)
docile (~> 1.1.0)
multi_json
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
thor (0.18.1)
timecop (0.6.1)
win32console (1.3.2-x86-mingw32)
xml-simple (1.1.3)
yard (0.8.7.3)
diff-lcs (1.1.2)
fakefs (0.2.1)
mime-types (1.16)
parka (0.3.1)
rest-client
thor
rake (0.8.7)
rcov (0.9.8)
rest-client (1.6.0)
mime-types (>= 1.16)
rr (0.10.11)
rspec (2.0.0.beta.19)
rspec-core (= 2.0.0.beta.19)
rspec-expectations (= 2.0.0.beta.19)
rspec-mocks (= 2.0.0.beta.19)
rspec-core (2.0.0.beta.19)
rspec-expectations (2.0.0.beta.19)
diff-lcs (>= 1.1.2)
rspec-mocks (2.0.0.beta.19)
term-ansicolor (1.0.5)
thor (0.13.8)
PLATFORMS
java
ruby
x86-mingw32
DEPENDENCIES
aws-s3
fakefs (~> 0.3.2)
foreman!
mime-types (~> 1.25.1)
posix-spawn (~> 0.3.6)
fakefs (~> 0.2.1)
parka
rake
rdiscount (~> 1.6.8)
ronn
rr (~> 1.0.2)
rspec (~> 2.0)
simplecov
timecop (= 0.6.1)
win32console (~> 1.3.0)
yard
rcov (~> 0.9.8)
rr (~> 0.10.11)
rspec (~> 2.0.0.beta.19)
term-ansicolor (~> 1.0.5)
thor (~> 0.13.6)

19
LICENSE
View File

@@ -1,19 +0,0 @@
Copyright (c) 2012 David Dollar
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
README.markdown Normal file
View File

@@ -0,0 +1,4 @@
Foreman
=======
See the [man page](http://ddollar.github.com/foreman) for usage.

View File

@@ -1,49 +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
* [forego](https://github.com/ddollar/forego) - Go
## Authors
#### Created and maintained by
David Dollar
#### Patches contributed by
[Contributor List](https://github.com/ddollar/foreman/contributors)
## License
Foreman is licensed under the MIT license.
See LICENSE for the full license text.

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__)
require "foreman"
require "bundler/setup"
task :default => :spec
task :release => :man
Dir[File.expand_path("../tasks/*.rake", __FILE__)].each do |task|
load task
desc "Run all specs"
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

1
autotest/discover.rb Normal file
View File

@@ -0,0 +1 @@
Autotest.add_discovery { "rspec2" }

View File

@@ -1,41 +0,0 @@
#!/bin/sh
#
#/ Usage: foreman-runner [-d <dir>] [-p] <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
}
read_profile=""
while getopts ":hd:p" OPT; do
case $OPT in
d) cd "$OPTARG" ;;
p) read_profile="1" ;;
h) usage ;;
\?) error "invalid option: -$OPTARG" ;;
:) error "option -$OPTARG requires an argument" ;;
esac
done
shift $((OPTIND-1))
[ -z "$1" ] && usage
if [ "$read_profile" = "1" ]; then
if [ -f .profile ]; then
. ./.profile
fi
fi
exec "$@"

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

View File

@@ -1,2 +0,0 @@
#!/bin/sh
export FOO=bar

View File

@@ -1,4 +0,0 @@
ticker: ruby ./ticker $PORT
error: ruby ./error
utf8: ruby ./utf8
spawner: ./spawner

View File

@@ -1,4 +0,0 @@
tick
tick
./never_die:6:in `sleep': Interrupt
from ./never_die:6

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

View File

@@ -1,7 +0,0 @@
#!/bin/sh
./spawnee A &
./spawnee B &
./spawnee C &
wait

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

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

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

View File

@@ -1,14 +0,0 @@
pre-start script
bash << "EOF"
mkdir -p <%= log %>
chown -R <%= user %> <%= log %>
mkdir -p <%= run %>
chown -R <%= user %> <%= run %>
EOF
end script
start on runlevel [2345]
stop on runlevel [016]

View File

@@ -1,8 +0,0 @@
start on starting <%= app %>-<%= name.gsub('_', '-') %>
stop on stopping <%= app %>-<%= name.gsub('_', '-') %>
respawn
env PORT=<%= port %><% engine.env.each_pair do |var, env| %>
env <%= var.upcase %>=<%= env %><% end %>
exec start-stop-daemon --start --chuid <%= user %> --chdir <%= engine.root %> --make-pidfile --pidfile <%= run %>/<%= app %>-<%= name %>-<%= num %>.pid --exec <%= executable %><%= arguments %> >> <%= log %>/<%= app %>-<%= name %>-<%= num %>.log 2>&1

View File

@@ -1,33 +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>EnvironmentVariables</key>
<dict>
<%- engine.env.merge("PORT" => port).each_pair do |var,env| -%>
<key><%= var.upcase %></key>
<string><%= env %></string>
<%- end -%>
</dict>
<key>ProgramArguments</key>
<array>
<%- command_args.each do |command| -%>
<string><%= command %></string>
<%- end -%>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
<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>

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"

View File

@@ -1,3 +0,0 @@
#!/bin/sh
cd <%= engine.root %>
exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %>

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(',') %>

View File

@@ -1,6 +0,0 @@
[Unit]
StopWhenUnneeded=true
Wants=<%= process_master_names.join(' ') %>
[Install]
WantedBy=multi-user.target

View File

@@ -1,15 +0,0 @@
[Unit]
StopWhenUnneeded=true
[Service]
User=<%= user %>
WorkingDirectory=<%= engine.root %>
Environment=PORT=<%= port %><% engine.env.each_pair do |var,env| %>
Environment=<%= var.upcase %>=<%= env %><% end %>
ExecStart=/bin/bash -lc '<%= process.command %>'
Restart=always
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=process

View File

@@ -1,3 +0,0 @@
[Unit]
StopWhenUnneeded=true
Wants=<%= process_names.join(' ') %>

View File

@@ -1,12 +0,0 @@
pre-start script
bash << "EOF"
mkdir -p <%= log %>
chown -R <%= user %> <%= log %>
EOF
end script
start on runlevel [2345]
stop on runlevel [016]

View File

@@ -1,5 +0,0 @@
start on starting <%= app %>-<%= name %>
stop on stopping <%= app %>-<%= name %>
respawn
exec su - <%= user %> -s /bin/sh -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> exec <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'

View File

@@ -1,2 +0,0 @@
start on starting <%= app %>
stop on stopping <%= app %>

33
dist/deb.rake vendored
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
dist/gem.rake vendored
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
dist/jruby.rake vendored
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
dist/mingw32.rake vendored
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
dist/pkg.rake vendored
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

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.

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

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-----

View File

@@ -1,3 +0,0 @@
#!/bin/sh
set -e
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman

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>

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>

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

View File

@@ -1,2 +0,0 @@
#!/bin/sh
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman

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
dist/tgz.rake vendored
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"), "bin/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

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env ruby
$stdout.sync = true
puts "will error in 10s"
sleep 5
raise "Dying"

6
example/ticker Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
while true
puts "tick: #{ARGV.inspect}"
sleep 1
end

View File

@@ -0,0 +1,8 @@
pre-start script
bash << "EOF"
mkdir -p <%= log_root %>
chown -R <%= user %> <%= log_root %>
EOF
end script

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"

View File

@@ -1,31 +1,13 @@
$:.unshift File.expand_path("../lib", __FILE__)
require "foreman/version"
require "rubygems"
require "parka/specification"
Gem::Specification.new do |gem|
Parka::Specification.new do |gem|
gem.name = "foreman"
gem.license = "MIT"
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.homepage = "http://github.com/ddollar/foreman"
gem.description = gem.summary
gem.executables = "foreman"
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'
gem.add_dependency 'dotenv', '>= 0.7'
if ENV["PLATFORM"] == "java"
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
gem.executables = "foreman"
gem.files << "man/foreman.1"
gem.files << Dir["export/**/*"]
end

View File

@@ -1,23 +1,8 @@
require "foreman/version"
module Foreman
VERSION = "0.7.4"
class AppDoesNotExist < Exception; end
def self.runner
File.expand_path("../../bin/foreman-runner", __FILE__)
end
def self.jruby_18?
defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java" and ruby_18?
end
def self.ruby_18?
defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
end
def self.windows?
defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
end
end

View File

@@ -1,116 +1,64 @@
require "foreman"
require "foreman/helpers"
require "foreman/engine"
require "foreman/engine/cli"
require "foreman/export"
require "foreman/version"
require "shellwords"
require "yaml"
require "thor"
class Foreman::CLI < Thor
include Foreman::Helpers
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: ./Procfile"
map ["-v", "--version"] => :version
desc "start [PROCESS]", "Start the application, or a specific process"
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
method_option :color, :type => :boolean, :aliases => "-c", :desc => "Force color to be enabled"
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"'
method_option :port, :type => :numeric, :aliases => "-p"
method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5."
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
method_option :port, :type => :numeric, :aliases => "-p"
method_option :concurrency, :type => :string, :aliases => "-c",
:banner => '"alpha=5,bar=3"'
def start(process=nil)
require_posix_spawn_for_ruby_18!
check_procfile!
load_environment!
engine.load_procfile(procfile)
engine.options[:formation] = "#{process}=1" if process
engine.start
if process
engine.execute(process, options)
else
engine.start(options)
end
end
desc "export FORMAT LOCATION", "Export the application to another process management format"
method_option :app, :type => :string, :aliases => "-a"
method_option :log, :type => :string, :aliases => "-l"
method_option :run, :type => :string, :aliases => "-r", :desc => "Specify the pid file directory, defaults to /var/run/<application>"
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 :user, :type => :string, :aliases => "-u"
method_option :template, :type => :string, :aliases => "-t"
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
method_option :concurrency, :type => :string, :aliases => "-c",
:banner => '"alpha=5,bar=3"'
def export(format, location=nil)
check_procfile!
load_environment!
engine.load_procfile(procfile)
formatter = Foreman::Export.formatter(format)
formatter.new(location, engine, options).export
formatter = case format
when "upstart" then Foreman::Export::Upstart
when "inittab" then Foreman::Export::Inittab
else error "Unknown export format: #{format}."
end
formatter.new(engine).export(location, options)
rescue Foreman::Export::Exception => ex
error ex.message
end
desc "check", "Validate your application's Procfile"
private ######################################################################
def check
check_procfile!
engine.load_procfile(procfile)
error "no processes defined" unless engine.processes.length > 0
puts "valid procfile detected (#{engine.process_names.join(', ')})"
def check_procfile!
error("Procfile does not exist.") unless File.exist?(procfile)
end
desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
def run(*args)
load_environment!
if File.exist?(procfile)
engine.load_procfile(procfile)
end
begin
engine.env.each { |k,v| ENV[k] = v }
if args.size == 1 && process = engine.process(args.first)
process.exec(:env => engine.env)
else
exec args.shelljoin
end
rescue Errno::EACCES
error "not executable: #{args.first}"
rescue Errno::ENOENT
error "command not found: #{args.first}"
end
def engine
@engine ||= Foreman::Engine.new(procfile)
end
desc "version", "Display Foreman gem version"
def version
puts Foreman::VERSION
end
no_tasks do
def engine
@engine ||= begin
engine_class = Foreman::Engine::CLI
engine = engine_class.new(options)
engine
end
end
def procfile
options[:procfile] || "./Procfile"
end
private ######################################################################
@@ -120,42 +68,8 @@ private ######################################################################
exit 1
end
def check_procfile!
error("#{procfile} does not exist.") unless 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 require_posix_spawn_for_ruby_18!
begin
Kernel.require 'posix/spawn' # Use Kernel explicitly so we can mock the require call in the spec
rescue LoadError
error "foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`."
end if Foreman.ruby_18?
end
def procfile
case
when options[:procfile] then options[:procfile]
when options[:root] then File.expand_path(File.join(options[: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))
def procfile_exists?(procfile)
File.exist?(procfile)
end
end

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

View File

@@ -1,421 +1,165 @@
require "foreman"
require "foreman/process"
require "foreman/procfile"
require "dotenv"
require "foreman/utils"
require "pty"
require "tempfile"
require "timeout"
require "term/ansicolor"
require "fileutils"
require "thread"
class Foreman::Engine
# The signals that the engine cares about.
#
HANDLED_SIGNALS = [ :TERM, :INT, :HUP ]
attr_reader :procfile
attr_reader :directory
attr_reader :env
attr_reader :options
attr_reader :processes
extend Term::ANSIColor
# Create an +Engine+ for running processes
#
# @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
COLORS = [ cyan, yellow, green, magenta, red ]
@options[:formation] ||= (options[:concurrency] || "all=1")
@options[:timeout] ||= 5
@env = {}
@mutex = Mutex.new
@names = {}
@processes = []
@running = {}
@readers = {}
# Self-pipe for deferred signal-handling (ala djb: http://cr.yp.to/docs/selfpipe.html)
reader, writer = create_pipe
reader.close_on_exec = true if reader.respond_to?(:close_on_exec)
writer.close_on_exec = true if writer.respond_to?(:close_on_exec)
@selfpipe = { :reader => reader, :writer => writer }
# Set up a global signal queue
# http://blog.rubybestpractices.com/posts/ewong/016-Implementing-Signal-Handlers.html
Thread.main[:signal_queue] = []
def initialize(procfile)
@procfile = read_procfile(procfile)
@directory = File.expand_path(File.dirname(procfile))
end
# Start the processes registered to this +Engine+
#
def start
register_signal_handlers
startup
spawn_processes
watch_for_output
sleep 0.1
watch_for_termination { terminate_gracefully }
shutdown
end
# Set up deferred signal handlers
#
def register_signal_handlers
HANDLED_SIGNALS.each do |sig|
if ::Signal.list.include? sig.to_s
trap(sig) { Thread.main[:signal_queue] << sig ; notice_signal }
def processes
@processes ||= begin
procfile.split("\n").inject({}) do |hash, line|
next if line.strip == ""
name, command = line.split(" ", 2)
process = Foreman::Process.new(name, command)
process.color = next_color
hash.update(process.name => process)
end
end
end
# Unregister deferred signal handlers
#
def restore_default_signal_handlers
HANDLED_SIGNALS.each do |sig|
trap(sig, :DEFAULT) if ::Signal.list.include? sig.to_s
def start(options={})
proctitle "ruby: foreman master"
processes.each do |name, process|
fork process, options
end
trap("TERM") { kill_and_exit("TERM") }
trap("INT") { kill_and_exit("INT") }
watch_for_termination
end
def execute(name, options={})
fork processes[name], options
trap("TERM") { kill_and_exit("TERM") }
trap("INT") { kill_and_exit("INT") }
watch_for_termination
end
def port_for(process, num, base_port=nil)
base_port ||= 5000
offset = processes.keys.sort.index(process.name) * 100
base_port.to_i + offset + num - 1
end
private ######################################################################
def fork(process, options={})
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
1.upto(concurrency[process.name]) do |num|
fork_individual(process, port_for(process, num, options[:port]))
end
end
# Wake the main thread up via the selfpipe when there's a signal
#
def notice_signal
@selfpipe[:writer].write_nonblock( '.' )
rescue Errno::EAGAIN
# Ignore writes that would block
rescue Errno::EINT
# Retry if another signal arrived while writing
retry
end
def fork_individual(process, port)
ENV["PORT"] = port.to_s
# Invoke the real handler for signal +sig+. This shouldn't be called directly
# by signal handlers, as it might invoke code which isn't re-entrant.
#
# @param [Symbol] sig the name of the signal to be handled
#
def handle_signal(sig)
case sig
when :TERM
handle_term_signal
when :INT
handle_interrupt
when :HUP
handle_hangup
else
system "unhandled signal #{sig}"
pid = Process.fork do
run(process)
end
info "started with pid #{pid}", process
running_processes[pid] = process
end
# Handle a TERM signal
#
def handle_term_signal
puts "SIGTERM received"
terminate_gracefully
end
def run(process, log_to_file=true)
proctitle "ruby: foreman #{process.name}"
# Handle an INT signal
#
def handle_interrupt
puts "SIGINT received"
terminate_gracefully
end
Dir.chdir directory do
FileUtils.mkdir_p "log"
command = process.command
# Handle a HUP signal
#
def handle_hangup
puts "SIGHUP received"
terminate_gracefully
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)
@env.update Dotenv::Environment.new(filename)
end
# Send a signal to all processes started by this +Engine+
#
# @param [String] signal The signal to send to each process
#
def kill_children( signal="SIGTERM" )
@running.each do |pid, (process, index)|
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
begin
Process.kill( signal, pid )
rescue Errno::ESRCH, Errno::EPERM => err
system " %p when sending signal %p to pid %d: %s" %
[ err.class, signal, pid, err.message ]
end
end
end
# Send a signal to the whole process group.
#
# @param [String] signal The signal to send
#
def killall(signal="SIGTERM")
kill_children(signal)
end
# Get the process formation
#
# @returns [Fixnum] The formation count for the specified process
#
def formation
@formation ||= parse_formation(options[:formation])
end
# List the available process names
#
# @returns [Array] A list of process names
#
def process_names
@processes.map { |p| @names[p] }
end
# Get the +Process+ for a specifid name
#
# @param [String] name The process name
#
# @returns [Foreman::Process] The +Process+ for the specified name
#
def process(name)
@names.invert[name]
end
# Yield each +Process+ in order
#
def each_process
process_names.each do |name|
yield name, process(name)
end
end
# Get the root directory for this +Engine+
#
# @returns [String] The root directory
#
def root
File.expand_path(options[:root] || Dir.pwd)
end
# Get the port for a given process and offset
#
# @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=nil)
if base
base + (@processes.index(process.process) * 100) + (instance - 1)
else
base_port + (@processes.index(process) * 100) + (instance - 1)
end
end
# Get the base port for this foreman instance
#
# @returns [Fixnum] port The base port
#
def base_port
(options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
end
# deprecated
def environment
env
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 create_pipe
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
end
def name_for(pid)
process, index = @running[pid]
name_for_index(process, index)
end
def name_for_index(process, index)
[ @names[process], index.to_s ].compact.join(".")
end
def parse_formation(formation)
pairs = 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
def output_with_mutex(name, message)
@mutex.synchronize do
output name, message
end
end
def system(message)
output_with_mutex "system", message
end
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,
"PS" => name_for_index(process, n)
})
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
loop do
io = IO.select([@selfpipe[:reader]] + @readers.values, nil, nil, 30)
begin
@selfpipe[:reader].read_nonblock(11)
rescue Errno::EAGAIN, Errno::EINTR => err
# ignore
end
# Look for any signals that arrived and handle them
while sig = Thread.main[:signal_queue].shift
self.handle_signal(sig)
end
(io.nil? ? [] : io.first).each do |reader|
next if reader == @selfpipe[:reader]
if reader.eof?
@readers.delete_if { |key, value| value == reader }
else
data = reader.gets
output_with_mutex name_for(@readers.invert[reader]), data
end
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
until stdin.eof?
info stdin.gets, process
end
end
rescue Exception => ex
puts ex.message
puts ex.backtrace
rescue PTY::ChildExited, Interrupt
info "process exiting", process
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
pid, status = Process.wait2
output_with_mutex name_for(pid), termination_message_for(status)
@running.delete(pid)
yield if block_given?
pid
rescue Errno::ECHILD
process = running_processes.delete(pid)
info "process terminated", process
kill_and_exit
end
def terminate_gracefully
return if @terminating
restore_default_signal_handlers
@terminating = true
if Foreman.windows?
system "sending SIGKILL to all processes"
kill_children "SIGKILL"
else
system "sending SIGTERM to all processes"
kill_children "SIGTERM"
end
Timeout.timeout(options[:timeout]) do
watch_for_termination while @running.length > 0
end
rescue Timeout::Error
system "sending SIGKILL to all processes"
kill_children "SIGKILL"
def running_processes
@running_processes ||= {}
end
def next_color
@current_color ||= -1
@current_color += 1
@current_color >= COLORS.length ? "" : COLORS[@current_color]
end
end

View File

@@ -1,104 +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, force=false)
io.extend(self)
@@color_force = force
end
def color?
return true if @@color_force
return false if Foreman.windows?
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 bright_cyan bright_yellow
bright_green bright_magenta bright_red bright_blue )
def startup
@colors = map_colors
proctitle "foreman: master" unless Foreman.windows?
Color.enable($stdout, options[:color])
end
def output(name, data)
data.to_s.lines.map(&:chomp).each do |message|
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
$stdout.flush
end
rescue Errno::EPIPE
terminate_gracefully
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"] = "bright_white"
colors
end
def proctitle(title)
$0 = title
end
def termtitle(title)
printf("\033]0;#{title}\007") unless Foreman.windows?
end
end

View File

@@ -1,36 +1,8 @@
require "foreman"
require "foreman/helpers"
require "pathname"
module Foreman::Export
extend Foreman::Helpers
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
require "foreman/export/base"
require "foreman/export/inittab"
require "foreman/export/upstart"
require "foreman/export/daemon"
require "foreman/export/bluepill"
require "foreman/export/runit"
require "foreman/export/supervisord"
require "foreman/export/launchd"
require "foreman/export/systemd"
require "foreman/export/inittab"

View File

@@ -1,87 +1,20 @@
require "foreman/export"
require "ostruct"
require "pathname"
require "shellwords"
require "foreman/utils"
class Foreman::Export::Base
attr_reader :location
attr_reader :engine
attr_reader :options
attr_reader :formation
# deprecated
attr_reader :port
def initialize(location, engine, options={})
@location = location
@engine = engine
@options = options.dup
@formation = engine.formation
# deprecated
def port
Foreman::Export::Base.warn_deprecation!
engine.base_port
end
# deprecated
def template
Foreman::Export::Base.warn_deprecation!
options[:template]
end
# deprecated
def @engine.procfile
Foreman::Export::Base.warn_deprecation!
@processes.map do |process|
OpenStruct.new(
:name => @names[process],
:process => process
)
end
end
def initialize(engine)
@engine = engine
end
def export
error("Must specify a location") unless location
FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
FileUtils.mkdir_p(run) rescue error("Could not create: #{run}")
FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
FileUtils.chown(user, nil, run) rescue error("Could not chown #{run} to #{user}")
end
def app
options[:app] || "app"
end
def log
options[:log] || "/var/log/#{app}"
end
def run
options[:run] || "/var/run/#{app}"
end
def user
options[:user] || app
raise "export method must be overridden"
end
private ######################################################################
def self.warn_deprecation!
@@deprecation_warned ||= false
return if @@deprecation_warned
puts "WARNING: Using deprecated exporter interface. Please update your exporter"
puts "the interface shown in the upstart exporter:"
puts
puts "https://github.com/ddollar/foreman/blob/master/lib/foreman/export/upstart.rb"
puts "https://github.com/ddollar/foreman/blob/master/data/export/upstart/process.conf.erb"
puts
@@deprecation_warned = true
end
def error(message)
raise Foreman::Export::Exception.new(message)
end
@@ -89,61 +22,14 @@ private ######################################################################
def say(message)
puts "[foreman export] %s" % message
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
# deprecated
def old_export_template(exporter, file, template_root)
if template_root && File.exist?(file_path = File.join(template_root, file))
File.read(file_path)
elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
File.read(file_path)
else
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
end
end
def export_template(name, file=nil, template_root=nil)
if file && template_root
old_export_template name, file, template_root
else
name_without_first = name.split("/")[1..-1].join("/")
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
end
def write_template(name, target, binding)
compiled = ERB.new(export_template(name), nil, '-').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))
def export_template(name)
File.read(File.expand_path("../../../../export/#{name}", __FILE__))
end
def write_file(filename, contents)
say "writing: #{filename}"
filename = File.join(location, filename) unless Pathname.new(filename).absolute?
File.open(filename, "w") do |file|
file.puts contents
end

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

View File

@@ -1,28 +0,0 @@
require "erb"
require "foreman/export"
class Foreman::Export::Daemon < Foreman::Export::Base
def export
super
(Dir["#{location}/#{app}-*.conf"] << "#{location}/#{app}.conf").each do |file|
clean file
end
write_template "daemon/master.conf.erb", "#{app}.conf", binding
engine.each_process do |name, process|
next if engine.formation[name] < 1
write_template "daemon/process_master.conf.erb", "#{app}-#{name}.conf", binding
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
arguments = process.command.split(" ")
executable = arguments.slice!(0)
arguments = arguments.size > 0 ? " -- #{arguments.join(' ')}" : ""
write_template "daemon/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
end
end
end
end

View File

@@ -1,41 +1,37 @@
require "foreman/export"
require "foreman/export/base"
class Foreman::Export::Inittab < Foreman::Export::Base
def export
error("Must specify a location") unless location
def export(fname=nil, options={})
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 << "# ----- foreman #{app} processes -----"
index = 1
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
engine.processes.values.inject(1) do |index, process|
1.upto(concurrency[process.name]) do |num|
id = app.slice(0, 2).upcase + sprintf("%02d", index)
port = engine.port_for(process, num)
commands = []
commands << "cd #{engine.root}"
commands << "export PORT=#{port}"
engine.env.each_pair do |var, env|
commands << "export #{var.upcase}=#{shell_quote(env)}"
end
commands << "#{process.command} >> #{log}/#{name}-#{num}.log 2>&1"
inittab << "#{id}:4:respawn:/bin/su - #{user} -c '#{commands.join(";")}'"
port = engine.port_for(process, num, options[:port])
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
index += 1
end
index
end
inittab << "# ----- end foreman #{app} processes -----"
inittab = inittab.join("\n") + "\n"
if location == "-"
puts inittab
if fname
FileUtils.mkdir_p(log_root)
FileUtils.chown(user, nil, log_root)
write_file(fname, inittab)
else
say "writing: #{location}"
File.open(location, "w") { |file| file.puts inittab }
puts inittab
end
end

View File

@@ -1,17 +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|
port = engine.port_for(process, num)
command_args = process.command.split(" ")
write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
end
end
end
end

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

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

View File

@@ -1,32 +0,0 @@
require "erb"
require "foreman/export"
class Foreman::Export::Systemd < Foreman::Export::Base
def export
super
Dir["#{location}/#{app}*.target"].concat(Dir["#{location}/#{app}*.service"]).each do |file|
clean file
end
process_master_names = []
engine.each_process do |name, process|
next if engine.formation[name] < 1
process_names = []
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
write_template "systemd/process.service.erb", "#{app}-#{name}-#{num}.service", binding
process_names << "#{app}-#{name}-#{num}.service"
end
write_template "systemd/process_master.target.erb", "#{app}-#{name}.target", binding
process_master_names << "#{app}-#{name}.target"
end
write_template "systemd/master.target.erb", "#{app}.target", binding
end
end

View File

@@ -1,25 +1,41 @@
require "erb"
require "foreman/export"
require "foreman/export/base"
class Foreman::Export::Upstart < Foreman::Export::Base
def export
super
def export(location, options={})
error("Must specify a location") unless location
(Dir["#{location}/#{app}-*.conf"] << "#{location}/#{app}.conf").each do |file|
clean file
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|
say "cleaning up: #{file}"
FileUtils.rm(file)
end
write_template "upstart/master.conf.erb", "#{app}.conf", binding
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
engine.each_process do |name, process|
next if engine.formation[name] < 1
write_template "upstart/process_master.conf.erb", "#{app}-#{name}.conf", binding
master_template = export_template("upstart/master.conf.erb")
master_config = ERB.new(master_template).result(binding)
write_file "#{location}/#{app}.conf", master_config
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
write_template "upstart/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
process_template = export_template("upstart/process.conf.erb")
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

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

View File

@@ -1,129 +1,14 @@
require "foreman"
require "shellwords"
class Foreman::Process
attr_reader :name
attr_reader :command
attr_reader :env
attr_accessor :color
# Create a Process
#
# @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={})
def initialize(name, command)
@name = name
@command = command
@options = options.dup
@options[:env] ||= {}
end
# Get environment-expanded command for a +Process+
#
# @param [Hash] custom_env ({}) Environment variables to merge with defaults
#
# @return [String] The expanded command
#
def expanded_command(custom_env={})
env = @options[:env].merge(custom_env)
expanded_command = command.dup
env.each do |key, val|
expanded_command.gsub!("$#{key}", val)
end
expanded_command
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].merge(options[:env] || {})
output = options[:output] || $stdout
runner = "#{Foreman.runner}".shellescape
if Foreman.windows?
Dir.chdir(cwd) do
Process.spawn env, expanded_command(env), :out => output, :err => output
end
elsif Foreman.jruby_18? || Foreman.ruby_18?
require "posix/spawn"
wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{expanded_command(env)}"
POSIX::Spawn.spawn(*spawn_args(env, wrapped_command.shellsplit, {:out => output, :err => output}))
else
wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{command}"
Process.spawn env, wrapped_command, :out => output, :err => output
end
end
# Exec a +Process+
#
# @param [Hash] options
#
# @option options :env ({}) Environment variables to set for this execution
#
# @return Does not return
def exec(options={})
env = @options[:env].merge(options[:env] || {})
env.each { |k, v| ENV[k] = v }
Dir.chdir(cwd)
Kernel.exec expanded_command(env)
end
# Send a signal to this +Process+
#
# @param [String] signal The signal to send
#
def kill(signal)
if Foreman.windows?
pid && Process.kill(signal, pid)
else
pid && Process.kill("-#{signal}", pid)
end
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
# Returns the working directory for this +Process+
#
# @returns [String]
#
def cwd
File.expand_path(@options[:cwd] || ".")
end
private
def spawn_args(env, argv, options)
args = []
args << env
args += argv
args << options
args
end
end

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).gsub("\r\n","\n").split("\n").map do |line|
if line =~ /^([A-Za-z0-9_-]+):\s*(.+)$/
[$1, $2]
end
end.compact
end
end

15
lib/foreman/utils.rb Normal file
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

View File

@@ -1,5 +0,0 @@
module Foreman
VERSION = "0.63.0"
end

View File

@@ -1,261 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "FOREMAN" "1" "May 2013" "Foreman 0.63.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\.
.
.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\-\-root\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
launchd
.
.IP "\(bu" 4
runit
.
.IP "\(bu" 4
supervisord
.
.IP "\(bu" 4
systemd
.
.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 "SYSTEMD EXPORT"
Will create a series of systemd scripts in the location you specify\. Scripts will be structured to make the following commands valid:
.
.P
\fBsystemctl start appname\.target\fR
.
.P
\fBsystemctl stop appname\-processname\.target\fR
.
.P
\fBsystemctl restart appname\-processname\-3\.service\fR
.
.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 and 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 \-f ~/myapp/Procfile
.
.fi
.
.IP "" 0
.
.SH "COPYRIGHT"
Foreman is Copyright (C) 2010 David Dollar \fIhttp://daviddollar\.org\fR

View File

@@ -4,12 +4,11 @@ foreman(1) -- manage Procfile-based applications
## SYNOPSIS
`foreman start [process]`<br>
`foreman run <command>`<br>
`foreman export <format> [location]`
## 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
your application directly or export it to some other process management
format.
@@ -30,19 +29,10 @@ The following options control how the application is run:
Specify the number of each process type to run. The value passed in
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`:
Specify which port to use as the base for this application. Should be
a multiple of 1000.
`foreman run` is used to run one-off commands using the same environment
as your defined processes.
## EXPORTING
`foreman export` is used to export your application to another process
@@ -68,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
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`:
Specify the user the application should be run as. Defaults to the
app name
## GLOBAL OPTIONS
## OPTIONS
These options control all modes of foreman's operation.
* `-d`, `--root`:
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`:
Specify an alternate location for the application's Procfile. This file's
containing directory will be assumed to be the root directory of the
@@ -97,18 +75,8 @@ These options control all modes of foreman's operation.
foreman currently supports the following output formats:
* bluepill
* inittab
* launchd
* runit
* supervisord
* systemd
* upstart
## INITTAB EXPORT
@@ -120,17 +88,6 @@ Will export a chunk of inittab-compatible configuration:
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 -----
## SYSTEMD EXPORT
Will create a series of systemd scripts in the location you specify. Scripts
will be structured to make the following commands valid:
`systemctl start appname.target`
`systemctl stop appname-processname.target`
`systemctl restart appname-processname-3.service`
## UPSTART EXPORT
Will create a series of upstart scripts in the location you specify. Scripts
@@ -147,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
to run it.
web: bundle exec thin start
job: bundle exec rake jobs:work
A process name may contain letters, numbers and 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
web bundle exec thin start
job bundle exec rake jobs:work
## EXAMPLES
@@ -185,7 +119,7 @@ Export the application in upstart format:
Run one process type from the application defined in a specific Procfile:
$ foreman start alpha -f ~/myapp/Procfile
$ foreman start alpha -p ~/app/Procfile
## COPYRIGHT

View File

@@ -1,25 +1,12 @@
require "spec_helper"
require "foreman/cli"
describe "Foreman::CLI", :fakefs do
describe "Foreman::CLI" do
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 "when a Procfile doesnt exist", :fakefs do
it "displays an error" do
describe "with a non-existent Procfile" do
it "prints an error" do
mock_error(subject, "Procfile does not exist.") do
dont_allow.instance_of(Foreman::Engine).start
subject.start
@@ -27,85 +14,48 @@ describe "Foreman::CLI", :fakefs do
end
end
describe "with a valid Procfile" do
it "can run a single command" do
without_fakefs do
output = foreman("start env -f #{resource_path("Procfile")}")
output.should =~ /env.1/
output.should_not =~ /test.1/
end
end
describe "with a Procfile" do
before(:each) { write_procfile }
it "can run all commands" do
without_fakefs do
output = foreman("start -f #{resource_path("Procfile")} -e #{resource_path(".env")}")
output.should =~ /echo.1 \| echoing/
output.should =~ /env.1 \| bar/
output.should =~ /test.1 \| testing/
end
end
it "sets PS variable with the process name" do
without_fakefs do
output = foreman("start -f #{resource_path("Procfile")}")
output.should =~ /ps.1 \| PS env var is ps.1/
end
it "runs successfully" do
dont_allow(subject).error
mock.instance_of(Foreman::Engine).start({})
subject.start
end
end
end
describe "check" do
it "with a valid Procfile displays the jobs" do
write_procfile
foreman("check").should == "valid procfile detected (alpha, bravo, foo_bar, foo-bar)\n"
describe "export" do
describe "with a non-existent Procfile" do
it "prints an error" do
mock_error(subject, "Procfile does not exist.") do
dont_allow.instance_of(Foreman::Engine).export
subject.export("testapp")
end
end
end
it "with a blank Procfile displays an error" do
FileUtils.touch "Procfile"
foreman("check").should == "ERROR: no processes defined\n"
end
describe "with a Procfile" do
before(:each) { write_procfile }
it "without a Procfile displays an error" do
FileUtils.rm_f "Procfile"
foreman("check").should == "ERROR: Procfile does not exist.\n"
describe "with an invalid formatter" do
it "prints an error" do
mock_error(subject, "Unknown export format: invalidformatter.") do
subject.export("invalidformatter")
end
end
end
describe "with a valid config" do
before(:each) { write_foreman_config("testapp") }
it "runs successfully" do
dont_allow(subject).error
mock.instance_of(Foreman::Export::Upstart).export("/tmp/foo", {})
subject.export("upstart", "/tmp/foo")
end
end
end
end
describe "run" do
it "can run a command" do
forked_foreman("run echo 1").should == "1\n"
end
it "includes the environment" do
forked_foreman("run #{resource_path("bin/env FOO")} -e #{resource_path(".env")}").should == "bar\n"
end
it "can run a command from the Procfile" do
forked_foreman("run -f #{resource_path("Procfile")} test").should == "testing\n"
end
it "exits with the same exit code as the command" do
fork_and_get_exitstatus("run echo 1").should == 0
fork_and_get_exitstatus("run date 'invalid_date'").should == 1
end
end
describe "version" do
it "displays gem version" do
foreman("version").chomp.should == Foreman::VERSION
end
it "displays gem version on shortcut command" do
foreman("-v").chomp.should == Foreman::VERSION
end
end
describe "when posix-spawn is not present on ruby 1.8" do
it "should fail with an error" do
mock(Kernel).require('posix/spawn') { raise LoadError }
output = foreman("start -f #{resource_path("Procfile")}")
output.should == "ERROR: foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`.\n"
end
end if running_ruby_18?
end

View File

@@ -1,112 +1,50 @@
require "spec_helper"
require "foreman/engine"
class Foreman::Engine::Tester < Foreman::Engine
attr_reader :buffer
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 "Foreman::Engine" do
subject { Foreman::Engine.new("Procfile") }
describe "initialize" do
describe "with a Procfile" do
before { write_procfile }
describe "without an existing Procfile" do
it "raises an error" do
lambda { subject }.should raise_error
end
end
describe "with a Procfile" do
it "reads the processes" do
subject.process("alpha").command.should == "./alpha"
subject.process("bravo").command.should == "./bravo"
write_procfile
subject.processes["alpha"].command.should == "./alpha"
subject.processes["bravo"].command.should == "./bravo"
end
end
end
describe "start" do
it "forks the processes" do
mock(subject.process("alpha")).run(anything)
mock(subject.process("bravo")).run(anything)
mock(subject).watch_for_output
write_procfile
mock(subject).fork(subject.processes["alpha"], {})
mock(subject).fork(subject.processes["bravo"], {})
mock(subject).watch_for_termination
subject.start
end
it "handles concurrency" do
subject.options[:formation] = "alpha=2"
mock(subject.process("alpha")).run(anything).twice
mock(subject.process("bravo")).run(anything).never
mock(subject).watch_for_output
write_procfile
mock(subject).fork_individual(subject.processes["alpha"], 5000)
mock(subject).fork_individual(subject.processes["alpha"], 5001)
mock(subject).fork_individual(subject.processes["bravo"], 5100)
mock(subject).watch_for_termination
subject.start
subject.start(:concurrency => "alpha=2")
end
end
describe "directories" do
it "has the directory default relative to the Procfile" do
write_procfile "/some/app/Procfile"
engine = Foreman::Engine.new.load_procfile("/some/app/Procfile")
engine.root.should == "/some/app"
describe "execute" do
it "runs the processes" do
write_procfile
mock(subject).fork(subject.processes["alpha"], {})
mock(subject).watch_for_termination
subject.execute("alpha")
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 handle multiline strings" do
File.open("/tmp/env", "w") do |f|
f.puts 'FOO="bar\nbaz"'
end
subject.load_env "/tmp/env"
subject.env["FOO"].should == "bar\nbaz"
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

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

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

View File

@@ -1,97 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/daemon"
require "tmpdir"
describe Foreman::Export::Daemon, :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(:daemon) { Foreman::Export::Daemon.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("daemon") }
before(:each) { stub(daemon).say }
it "exports to the filesystem" do
daemon.export
File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
File.read("/tmp/init/app-bravo.conf").should == example_export_file("daemon/app-bravo.conf")
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("daemon/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")
mock(FileUtils).rm("/tmp/init/app-foo-bar.conf")
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.conf")
mock(FileUtils).rm("/tmp/init/app-foo_bar.conf")
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.conf")
daemon.export
daemon.export
end
it "does not delete exported files for similarly named applications" do
FileUtils.mkdir_p "/tmp/init"
["app2", "app2-alpha", "app2-alpha-1"].each do |name|
path = "/tmp/init/#{name}.conf"
FileUtils.touch(path)
dont_allow(FileUtils).rm(path)
end
daemon.export
end
context "with a formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
daemon.export
File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("daemon/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
daemon.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/daemon")
File.open(File.expand_path("~/.foreman/templates/daemon/master.conf.erb"), "w") do |file|
file.puts "default_alternate_template"
end
end
it "can export with alternate template files" do
daemon.export
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
end
end
end

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

View File

@@ -1,31 +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
context "with multiple command arguments" do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", "charlie") }
it "splits each command argument" do
launchd.export
File.read("/tmp/init/app-alpha-1.plist").should == example_export_file("launchd/launchd-c.default")
end
end
end

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
end
end

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

View File

@@ -1,91 +0,0 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/systemd"
require "tmpdir"
describe Foreman::Export::Systemd, :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(:systemd) { Foreman::Export::Systemd.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("systemd") }
before(:each) { stub(systemd).say }
it "exports to the filesystem" do
systemd.export
File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
File.read("/tmp/init/app-bravo.target").should == example_export_file("systemd/app-bravo.target")
File.read("/tmp/init/app-bravo-1.service").should == example_export_file("systemd/app-bravo-1.service")
end
it "cleans up if exporting into an existing dir" do
mock(FileUtils).rm("/tmp/init/app.target")
mock(FileUtils).rm("/tmp/init/app-alpha.target")
mock(FileUtils).rm("/tmp/init/app-alpha-1.service")
mock(FileUtils).rm("/tmp/init/app-bravo.target")
mock(FileUtils).rm("/tmp/init/app-bravo-1.service")
mock(FileUtils).rm("/tmp/init/app-foo-bar.target")
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.service")
mock(FileUtils).rm("/tmp/init/app-foo_bar.target")
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.service")
systemd.export
systemd.export
end
it "includes environment variables" do
engine.env['KEY'] = 'some "value"'
systemd.export
File.read("/tmp/init/app-alpha-1.service").should =~ /KEY=some "value"$/
end
context "with a formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
systemd.export
File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
File.read("/tmp/init/app-alpha-2.service").should == example_export_file("systemd/app-alpha-2.service")
File.exists?("/tmp/init/app-bravo-1.service").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.target.erb", "w") { |f| f.puts "alternate_template" }
end
it "can export with alternate template files" do
systemd.export
File.read("/tmp/init/app.target").should == "alternate_template\n"
end
end
context "with alternate templates from home dir" do
before do
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/systemd")
File.open(File.expand_path("~/.foreman/templates/systemd/master.target.erb"), "w") do |file|
file.puts "default_alternate_template"
end
end
it "can export with alternate template files" do
systemd.export
File.read("/tmp/init/app.target").should == "default_alternate_template\n"
end
end
end

View File

@@ -1,104 +1,2 @@
require "spec_helper"
require "foreman/engine"
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")
mock(FileUtils).rm("/tmp/init/app-foo-bar.conf")
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.conf")
mock(FileUtils).rm("/tmp/init/app-foo_bar.conf")
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.conf")
upstart.export
upstart.export
end
it "does not delete exported files for similarly named applications" do
FileUtils.mkdir_p "/tmp/init"
["app2", "app2-alpha", "app2-alpha-1"].each do |name|
path = "/tmp/init/#{name}.conf"
FileUtils.touch(path)
dont_allow(FileUtils).rm(path)
end
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

View File

@@ -1,24 +1,2 @@
require "spec_helper"
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

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

View File

@@ -1,48 +1,2 @@
require 'spec_helper'
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 == (Foreman.ruby_18? ? "\xFF\x03\n" : "\xFF\x03\n".force_encoding('binary'))
end
end
end
require "spec_helper"
require "foreman/process"

View File

@@ -1,43 +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"
procfile["foo-bar"].should == "./foo-bar"
procfile["foo_bar"].should == "./foo_bar"
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

View File

@@ -8,9 +8,4 @@ describe Foreman do
it { should be_a String }
end
describe "runner" do
it "should exist" do
File.exists?(Foreman.runner).should == true
end
end
end

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

View File

@@ -1 +0,0 @@
FOO=bar

View File

@@ -1,5 +0,0 @@
echo: bin/echo echoing
env: bin/env FOO
test: bin/test
utf8: bin/utf8
ps: bin/echo PS env var is $PS

View File

@@ -1,2 +0,0 @@
#!/bin/sh
echo $*

View File

@@ -1,2 +0,0 @@
#!/bin/bash
echo ${!1}

View File

@@ -1,2 +0,0 @@
#!/bin/sh
echo "testing"

View File

@@ -1,2 +0,0 @@
#!/usr/bin/env ruby
puts "\xff\x03"

View File

@@ -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

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