Compare commits
140 Commits
jruby
...
v0.48.0.pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b704ad852 | ||
|
|
43428175c3 | ||
|
|
87f65e489c | ||
|
|
e5521f2cbb | ||
|
|
8b18143281 | ||
|
|
e06886ed57 | ||
|
|
d6a00d7262 | ||
|
|
d9d1346640 | ||
|
|
0df1a4d784 | ||
|
|
51a704939e | ||
|
|
f41cc552c7 | ||
|
|
065bbf1cd8 | ||
|
|
b628ddc608 | ||
|
|
f745b16217 | ||
|
|
a87a882e60 | ||
|
|
6ba9252d0f | ||
|
|
efd9e2119b | ||
|
|
bf9bdbf118 | ||
|
|
929a138e54 | ||
|
|
d6514b4f33 | ||
|
|
8696a36833 | ||
|
|
3ea5de42aa | ||
|
|
4a13122082 | ||
|
|
9b2987c3f0 | ||
|
|
6274f99225 | ||
|
|
0b0324fed9 | ||
|
|
32db70b778 | ||
|
|
3db1ad6fbc | ||
|
|
003b466a17 | ||
|
|
2a896a0fb5 | ||
|
|
6a8c81a38b | ||
|
|
a83dab363e | ||
|
|
53e0f4ecf9 | ||
|
|
e0fe5baf1b | ||
|
|
1aa1f15b8f | ||
|
|
fa46a605bb | ||
|
|
3077857ab7 | ||
|
|
08aa8f9d5d | ||
|
|
771489dec9 | ||
|
|
053ae8f0be | ||
|
|
af58af1c60 | ||
|
|
9075d93370 | ||
|
|
0c2e9df722 | ||
|
|
047f1ff5c4 | ||
|
|
26b54a62c5 | ||
|
|
7b85ad7c1a | ||
|
|
91a87049db | ||
|
|
f46408e8be | ||
|
|
93f04e42ac | ||
|
|
97c4582acc | ||
|
|
5689d75a87 | ||
|
|
1325b6750e | ||
|
|
638005403f | ||
|
|
55f274532f | ||
|
|
66f76c2036 | ||
|
|
865cabb525 | ||
|
|
7c3c4bc58f | ||
|
|
2dbe8c733b | ||
|
|
84352b82cc | ||
|
|
383c1f87af | ||
|
|
ce3003b026 | ||
|
|
e06f4b2f9e | ||
|
|
b721fd894e | ||
|
|
b8ea6fd4b3 | ||
|
|
dbda63263b | ||
|
|
93cdc31be0 | ||
|
|
4dfbe46690 | ||
|
|
0033f9caeb | ||
|
|
74839800a9 | ||
|
|
b75337e21e | ||
|
|
d94f941189 | ||
|
|
0b34f067cb | ||
|
|
dbe51832b0 | ||
|
|
3a2a53be95 | ||
|
|
b2bf95479e | ||
|
|
48f764e347 | ||
|
|
de62d0655e | ||
|
|
38aecff886 | ||
|
|
e4a3215257 | ||
|
|
c705b5fbef | ||
|
|
2fcb64959b | ||
|
|
69216b4c5e | ||
|
|
5d2930745a | ||
|
|
a5465bf55e | ||
|
|
8fc3d1ef24 | ||
|
|
d33e4fb0ed | ||
|
|
39b48b566f | ||
|
|
2f982ff9c7 | ||
|
|
1217ef1b56 | ||
|
|
c9943d70ec | ||
|
|
08dca57eb4 | ||
|
|
bb3377407a | ||
|
|
084b9493d1 | ||
|
|
279a251c78 | ||
|
|
a49ef286e8 | ||
|
|
0b3da59947 | ||
|
|
1f725dd68a | ||
|
|
2272f76479 | ||
|
|
cf4762071c | ||
|
|
d86b0bed1f | ||
|
|
12f825204b | ||
|
|
62c9d1db45 | ||
|
|
1127551369 | ||
|
|
8cb58b8517 | ||
|
|
d03e931b67 | ||
|
|
9ba2b32b36 | ||
|
|
cf5689a77a | ||
|
|
c23dbb79af | ||
|
|
7e55d8d3e2 | ||
|
|
d4f23d45a4 | ||
|
|
93fa1645e7 | ||
|
|
7bdada4a10 | ||
|
|
2b47d24ab7 | ||
|
|
24695348fb | ||
|
|
38b6482af5 | ||
|
|
501bc138c5 | ||
|
|
df8c05cd6c | ||
|
|
edcc4f3567 | ||
|
|
3759dbb463 | ||
|
|
b673931c05 | ||
|
|
137e43b040 | ||
|
|
79211d9bbf | ||
|
|
16d4b84a5d | ||
|
|
5ea4537046 | ||
|
|
d51433ff82 | ||
|
|
54ab74d305 | ||
|
|
e8b8f34f41 | ||
|
|
7535f1d3d8 | ||
|
|
3af0dfb4ae | ||
|
|
78547b8175 | ||
|
|
976fbc0bb0 | ||
|
|
28a9aa774f | ||
|
|
51f5ff3842 | ||
|
|
e1e18f62bb | ||
|
|
0a09117328 | ||
|
|
c3df12746f | ||
|
|
2ec6a23fb3 | ||
|
|
0d6b784de1 | ||
|
|
2dcd2c03db | ||
|
|
9d6d0bbb7d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
/.bundle
|
/.bundle
|
||||||
|
/.rbenv-version
|
||||||
|
/.yardoc
|
||||||
/coverage
|
/coverage
|
||||||
/example/log/*
|
/example/log/*
|
||||||
/man/*.html
|
/man/*.html
|
||||||
|
|||||||
21
.travis.yml
21
.travis.yml
@@ -1,12 +1,11 @@
|
|||||||
script: bundle exec rake spec
|
script: bundle exec rake spec
|
||||||
|
|
||||||
env: JRUBY_OPTS="--debug -X+O"
|
matrix:
|
||||||
|
allow_failures:
|
||||||
rvm:
|
- rvm: 1.8.7
|
||||||
- 1.8.7
|
- rvm: jruby
|
||||||
- 1.9.2
|
- rvm: rbx
|
||||||
- 1.9.3
|
- rvm: ree
|
||||||
- jruby-head
|
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
@@ -15,3 +14,11 @@ notifications:
|
|||||||
on_failure: always
|
on_failure: always
|
||||||
urls:
|
urls:
|
||||||
- http://dx-helper.herokuapp.com/travis
|
- http://dx-helper.herokuapp.com/travis
|
||||||
|
|
||||||
|
rvm:
|
||||||
|
- 1.8.7
|
||||||
|
- 1.9.2
|
||||||
|
- 1.9.3
|
||||||
|
- jruby
|
||||||
|
- rbx
|
||||||
|
- ree
|
||||||
|
|||||||
70
Changelog.md
70
Changelog.md
@@ -1,3 +1,73 @@
|
|||||||
|
## 0.48.0.pre1 (2012-06-11)
|
||||||
|
|
||||||
|
* Massive refactoring for programmatic control and stability [David Dollar]
|
||||||
|
* Procfile commands with shell interpolations now work again [David Dollar]
|
||||||
|
* Stop trying to test on Ruby 1.8 [David Dollar]
|
||||||
|
|
||||||
|
## 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)
|
## 0.37.2 (2012-01-29)
|
||||||
|
|
||||||
* handle directories with spaces in runner [David Dollar]
|
* handle directories with spaces in runner [David Dollar]
|
||||||
|
|||||||
4
Gemfile
4
Gemfile
@@ -11,11 +11,13 @@ platform :jruby do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'parka'
|
gem 'aws-s3'
|
||||||
gem 'rake'
|
gem 'rake'
|
||||||
gem 'ronn'
|
gem 'ronn'
|
||||||
gem 'fakefs', '~> 0.3.2'
|
gem 'fakefs', '~> 0.3.2'
|
||||||
gem 'rr', '~> 1.0.2'
|
gem 'rr', '~> 1.0.2'
|
||||||
gem 'rspec', '~> 2.0'
|
gem 'rspec', '~> 2.0'
|
||||||
gem "simplecov", :require => false
|
gem "simplecov", :require => false
|
||||||
|
gem 'timecop'
|
||||||
|
gem 'yard'
|
||||||
end
|
end
|
||||||
|
|||||||
29
Gemfile.lock
29
Gemfile.lock
@@ -1,30 +1,27 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
foreman (0.37.2)
|
foreman (0.48.0.pre2)
|
||||||
term-ansicolor (~> 1.0.7)
|
|
||||||
thor (>= 0.13.6)
|
thor (>= 0.13.6)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: http://rubygems.org/
|
remote: http://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
crack (0.1.8)
|
aws-s3 (0.6.2)
|
||||||
|
builder
|
||||||
|
mime-types
|
||||||
|
xml-simple
|
||||||
|
builder (3.0.0)
|
||||||
diff-lcs (1.1.3)
|
diff-lcs (1.1.3)
|
||||||
fakefs (0.3.2)
|
fakefs (0.3.2)
|
||||||
hpricot (0.8.2)
|
hpricot (0.8.6)
|
||||||
hpricot (0.8.2-java)
|
hpricot (0.8.6-java)
|
||||||
mime-types (1.16)
|
mime-types (1.16)
|
||||||
multi_json (1.0.4)
|
multi_json (1.0.4)
|
||||||
mustache (0.11.2)
|
mustache (0.11.2)
|
||||||
parka (0.6.2)
|
|
||||||
crack
|
|
||||||
rest-client
|
|
||||||
thor
|
|
||||||
posix-spawn (0.3.6)
|
posix-spawn (0.3.6)
|
||||||
rake (0.9.2.2)
|
rake (0.9.2.2)
|
||||||
rdiscount (1.6.5)
|
rdiscount (1.6.5)
|
||||||
rest-client (1.6.1)
|
|
||||||
mime-types (>= 1.16)
|
|
||||||
ronn (0.7.3)
|
ronn (0.7.3)
|
||||||
hpricot (>= 0.8.2)
|
hpricot (>= 0.8.2)
|
||||||
mustache (>= 0.7.0)
|
mustache (>= 0.7.0)
|
||||||
@@ -42,9 +39,11 @@ GEM
|
|||||||
multi_json (~> 1.0.3)
|
multi_json (~> 1.0.3)
|
||||||
simplecov-html (~> 0.5.3)
|
simplecov-html (~> 0.5.3)
|
||||||
simplecov-html (0.5.3)
|
simplecov-html (0.5.3)
|
||||||
term-ansicolor (1.0.7)
|
thor (0.15.2)
|
||||||
thor (0.14.6)
|
timecop (0.3.5)
|
||||||
win32console (1.3.0-x86-mingw32)
|
win32console (1.3.0-x86-mingw32)
|
||||||
|
xml-simple (1.0.15)
|
||||||
|
yard (0.8.2)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
java
|
java
|
||||||
@@ -52,13 +51,15 @@ PLATFORMS
|
|||||||
x86-mingw32
|
x86-mingw32
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
aws-s3
|
||||||
fakefs (~> 0.3.2)
|
fakefs (~> 0.3.2)
|
||||||
foreman!
|
foreman!
|
||||||
parka
|
|
||||||
posix-spawn (~> 0.3.6)
|
posix-spawn (~> 0.3.6)
|
||||||
rake
|
rake
|
||||||
ronn
|
ronn
|
||||||
rr (~> 1.0.2)
|
rr (~> 1.0.2)
|
||||||
rspec (~> 2.0)
|
rspec (~> 2.0)
|
||||||
simplecov
|
simplecov
|
||||||
|
timecop
|
||||||
win32console (~> 1.3.0)
|
win32console (~> 1.3.0)
|
||||||
|
yard
|
||||||
|
|||||||
@@ -27,13 +27,19 @@ Manage Procfile-based applications
|
|||||||
* [wiki](http://github.com/ddollar/foreman/wiki)
|
* [wiki](http://github.com/ddollar/foreman/wiki)
|
||||||
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
|
||||||
|
* [shoreman](https://github.com/hecticjeff/shoreman) - shell
|
||||||
|
* [honcho](https://github.com/nickstenning/honcho) - python
|
||||||
|
* [norman](https://github.com/josh/norman) - node.js
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
#### Created and maintained by
|
#### Created and maintained by
|
||||||
David Dollar
|
David Dollar
|
||||||
|
|
||||||
#### Patches contributed by
|
#### Patches contributed by
|
||||||
Adam Wiggins, Chris Continanza, Chris Lowder, Craig R Webster, Dan Farina, Dan Peterson, David Dollar, Fletcher Nichol, Florian Apolloner, Gabriel Burt, Gamaliel Toro, Greg Reinacker, Hugues Le Gendre, Hunter Nield, Iain Hecker, Jay Zeschin, Keith Rarick, Khaja Minhajuddin, Lincoln Stoll, Marcos Muino Garcia, Mark McGranaghan, Matt Griffin, Matt Haynes, Matthijs Langenberg, Michael Dwan, Michael van Rooijen, Mike Javorski, Nathan Broadbent, Nathan L Smith, Nick Zadrozny, Phil Hagelberg, Ricardo Chimal, Jr, Thom May, Tom Ward, brainopia, clifff, jc00ke
|
[Contributor List](https://github.com/ddollar/foreman/contributors)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
2
Rakefile
2
Rakefile
@@ -1,6 +1,8 @@
|
|||||||
$:.unshift File.expand_path("../lib", __FILE__)
|
$:.unshift File.expand_path("../lib", __FILE__)
|
||||||
require "foreman"
|
require "foreman"
|
||||||
|
|
||||||
|
require "bundler/setup"
|
||||||
|
|
||||||
Dir[File.expand_path("../tasks/*.rake", __FILE__)].each do |task|
|
Dir[File.expand_path("../tasks/*.rake", __FILE__)].each do |task|
|
||||||
load task
|
load task
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
#
|
#
|
||||||
#/ Usage: runner [-d <dir>] <command>
|
#/ Usage: foreman-runner [-d <dir>] <command> [<args>...]
|
||||||
#/
|
#/
|
||||||
#/ Run a command with exec, optionally changing directory first
|
#/ Run a command with exec, optionally changing directory first
|
||||||
|
|
||||||
@@ -27,10 +27,6 @@ done
|
|||||||
|
|
||||||
shift $((OPTIND-1))
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
command=$1
|
[ -z "$1" ] && usage
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
exec "$@"
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec $1
|
|
||||||
8
bin/taskman
Executable file
8
bin/taskman
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
$:.unshift File.expand_path("../../lib", __FILE__)
|
||||||
|
|
||||||
|
require "foreman/cli"
|
||||||
|
|
||||||
|
Foreman::CLI.engine_class = Foreman::TmuxEngine
|
||||||
|
Foreman::CLI.start
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
ticker: ruby ./ticker $PORT
|
ticker: ruby ./ticker $PORT
|
||||||
error: ruby ./error
|
error: ruby ./error
|
||||||
utf8: ruby ./utf8
|
utf8: ruby ./utf8
|
||||||
|
spawner: ./spawner
|
||||||
|
|||||||
14
data/example/spawnee
Executable file
14
data/example/spawnee
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
NAME="$1"
|
||||||
|
|
||||||
|
sigterm() {
|
||||||
|
echo "$NAME: got sigterm"
|
||||||
|
}
|
||||||
|
|
||||||
|
#trap sigterm SIGTERM
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
echo "$NAME: ping"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
7
data/example/spawner
Executable file
7
data/example/spawner
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
./spawnee A &
|
||||||
|
./spawnee B &
|
||||||
|
./spawnee C &
|
||||||
|
|
||||||
|
wait
|
||||||
@@ -3,24 +3,25 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|||||||
app.uid = "<%= user %>"
|
app.uid = "<%= user %>"
|
||||||
app.gid = "<%= user %>"
|
app.gid = "<%= user %>"
|
||||||
|
|
||||||
<% engine.procfile.entries.each do |process| %>
|
<% engine.each_process do |name, process| %>
|
||||||
<% 1.upto(concurrency[process.name]) do |num| %>
|
<% 1.upto(engine.formation[name]) do |num| %>
|
||||||
<% port = engine.port_for(process, num, self.port) %>
|
<% port = engine.port_for(process, num) %>
|
||||||
app.process("<%= process.name %>-<%=num%>") do |process|
|
app.process("<%= name %>-<%= num %>") do |process|
|
||||||
process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
|
process.start_command = "<%= process.command %>"
|
||||||
|
|
||||||
process.working_dir = "<%= engine.directory %>"
|
process.working_dir = "<%= engine.root %>"
|
||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "<%= port %>"}
|
process.environment = <%= engine.env.merge("PORT" => port.to_s).inspect %>
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||||
|
process.stop_grace_time = 45.seconds
|
||||||
|
|
||||||
process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
|
process.stdout = process.stderr = "<%= log %>/<%= app %>-<%= name %>-<%= num %>.log"
|
||||||
|
|
||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "<%= app %>-<%= process.name %>"
|
process.group = "<%= app %>-<%= name %>"
|
||||||
end
|
end
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
22
data/export/launchd/launchd.plist.erb
Normal file
22
data/export/launchd/launchd.plist.erb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string><%= "#{app}-#{name}-#{num}" %></string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string><%= process.command %></string>
|
||||||
|
</array>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string><%= user %></string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string><%= engine.root %></string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
7
data/export/runit/log/run.erb
Normal file
7
data/export/runit/log/run.erb
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
LOG=<%= log %>/<%= name %>-<%= num %>
|
||||||
|
|
||||||
|
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
|
||||||
|
exec chpst -u <%= user %> svlogd "$LOG"
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
LOG=<%= log_root %>/<%= process.name %>-<%= num %>
|
|
||||||
|
|
||||||
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
|
|
||||||
exec chpst -u <%= user %> svlogd "$LOG"
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd <%= engine.directory %>
|
cd <%= engine.root %>
|
||||||
exec chpst -u <%= user %> -e <%= process_env_directory %> <%= process.command %>
|
exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %>
|
||||||
|
|||||||
27
data/export/supervisord/app.conf.erb
Normal file
27
data/export/supervisord/app.conf.erb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<%
|
||||||
|
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(',') %>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
pre-start script
|
pre-start script
|
||||||
|
|
||||||
bash << "EOF"
|
bash << "EOF"
|
||||||
mkdir -p <%= log_root %>
|
mkdir -p <%= log %>
|
||||||
chown -R <%= user %> <%= log_root %>
|
chown -R <%= user %> <%= log %>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
end script
|
end script
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
start on starting <%= app %>-<%= process.name %>
|
start on starting <%= app %>-<%= name %>
|
||||||
stop on stopping <%= app %>-<%= process.name %>
|
stop on stopping <%= app %>-<%= name %>
|
||||||
respawn
|
respawn
|
||||||
|
|
||||||
exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= env %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
|
exec su - <%= user %> -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'
|
||||||
|
|||||||
2
dist/deb.rake
vendored
2
dist/deb.rake
vendored
@@ -4,7 +4,7 @@ file pkg("/apt-#{version}/foreman-#{version}.deb") => distribution_files("deb")
|
|||||||
assemble_distribution
|
assemble_distribution
|
||||||
assemble_gems
|
assemble_gems
|
||||||
assemble resource("deb/foreman"), "bin/foreman", 0755
|
assemble resource("deb/foreman"), "bin/foreman", 0755
|
||||||
File.chmod 0755, "bin/runner"
|
File.chmod 0755, "bin/foreman-runner"
|
||||||
end
|
end
|
||||||
|
|
||||||
assemble resource("deb/control"), "control"
|
assemble resource("deb/control"), "control"
|
||||||
|
|||||||
2
dist/gem.rake
vendored
2
dist/gem.rake
vendored
@@ -10,5 +10,5 @@ task "gem:clean" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
task "gem:release" => "gem:build" do |t|
|
task "gem:release" => "gem:build" do |t|
|
||||||
sh "parka push -f #{pkg("foreman-#{version}.gem")}"
|
sh "gem push #{pkg("foreman-#{version}.gem")} || echo 'error'"
|
||||||
end
|
end
|
||||||
|
|||||||
6
dist/jruby.rake
vendored
6
dist/jruby.rake
vendored
@@ -1,5 +1,7 @@
|
|||||||
file pkg("foreman-#{version}-jruby.gem") => distribution_files do |t|
|
file pkg("foreman-#{version}-jruby.gem") => distribution_files do |t|
|
||||||
sh "env PLATFORM=java gem build foreman.gemspec"
|
Bundler.with_clean_env do
|
||||||
|
sh "env PLATFORM=java gem build foreman.gemspec"
|
||||||
|
end
|
||||||
sh "mv foreman-#{version}-java.gem #{t.name}"
|
sh "mv foreman-#{version}-java.gem #{t.name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -10,5 +12,5 @@ task "jruby:clean" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
task "jruby:release" => "jruby:build" do |t|
|
task "jruby:release" => "jruby:build" do |t|
|
||||||
sh "parka push -f #{pkg("foreman-#{version}-jruby.gem")}"
|
sh "gem push #{pkg("foreman-#{version}-jruby.gem")} || echo 'error'"
|
||||||
end
|
end
|
||||||
|
|||||||
6
dist/mingw32.rake
vendored
6
dist/mingw32.rake
vendored
@@ -1,5 +1,7 @@
|
|||||||
file pkg("foreman-#{version}-mingw32.gem") => distribution_files do |t|
|
file pkg("foreman-#{version}-mingw32.gem") => distribution_files do |t|
|
||||||
sh "env PLATFORM=mingw32 gem build foreman.gemspec"
|
Bundler.with_clean_env do
|
||||||
|
sh "env PLATFORM=mingw32 gem build foreman.gemspec"
|
||||||
|
end
|
||||||
sh "mv foreman-#{version}-mingw32.gem #{t.name}"
|
sh "mv foreman-#{version}-mingw32.gem #{t.name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -10,5 +12,5 @@ task "mingw32:clean" do
|
|||||||
end
|
end
|
||||||
|
|
||||||
task "mingw32:release" => "mingw32:build" do |t|
|
task "mingw32:release" => "mingw32:build" do |t|
|
||||||
sh "parka push -f #{pkg("foreman-#{version}-mingw32.gem")}"
|
sh "gem push #{pkg("foreman-#{version}-mingw32.gem")} || echo 'error'"
|
||||||
end
|
end
|
||||||
|
|||||||
15
dist/pkg.rake
vendored
15
dist/pkg.rake
vendored
@@ -13,7 +13,7 @@ file pkg("foreman-#{version}.pkg") => distribution_files do |t|
|
|||||||
|
|
||||||
mkdir_p "pkg"
|
mkdir_p "pkg"
|
||||||
mkdir_p "pkg/Resources"
|
mkdir_p "pkg/Resources"
|
||||||
mkdir_p "pkg/foreman-#{version}.pkg"
|
mkdir_p "pkg/foreman.pkg"
|
||||||
|
|
||||||
dist = File.read(resource("pkg/Distribution.erb"))
|
dist = File.read(resource("pkg/Distribution.erb"))
|
||||||
dist = ERB.new(dist).result(binding)
|
dist = ERB.new(dist).result(binding)
|
||||||
@@ -21,20 +21,21 @@ file pkg("foreman-#{version}.pkg") => distribution_files do |t|
|
|||||||
|
|
||||||
dist = File.read(resource("pkg/PackageInfo.erb"))
|
dist = File.read(resource("pkg/PackageInfo.erb"))
|
||||||
dist = ERB.new(dist).result(binding)
|
dist = ERB.new(dist).result(binding)
|
||||||
File.open("pkg/foreman-#{version}.pkg/PackageInfo", "w") { |f| f.puts dist }
|
File.open("pkg/foreman.pkg/PackageInfo", "w") { |f| f.puts dist }
|
||||||
|
|
||||||
mkdir_p "pkg/foreman-#{version}.pkg/Scripts"
|
mkdir_p "pkg/foreman.pkg/Scripts"
|
||||||
cp resource("pkg/postinstall"), "pkg/foreman-#{version}.pkg/Scripts/postinstall"
|
cp resource("pkg/postinstall"), "pkg/foreman.pkg/Scripts/postinstall"
|
||||||
chmod 0755, "pkg/foreman-#{version}.pkg/Scripts/postinstall"
|
chmod 0755, "pkg/foreman.pkg/Scripts/postinstall"
|
||||||
|
|
||||||
sh %{ mkbom -s foreman pkg/foreman-#{version}.pkg/Bom }
|
sh %{ mkbom -s foreman pkg/foreman.pkg/Bom }
|
||||||
|
|
||||||
Dir.chdir("foreman") do
|
Dir.chdir("foreman") do
|
||||||
sh %{ pax -wz -x cpio . > ../pkg/foreman-#{version}.pkg/Payload }
|
sh %{ pax -wz -x cpio . > ../pkg/foreman.pkg/Payload }
|
||||||
end
|
end
|
||||||
|
|
||||||
sh %{ pkgutil --flatten pkg foreman-#{version}.pkg }
|
sh %{ pkgutil --flatten pkg foreman-#{version}.pkg }
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(File.dirname(t.name))
|
||||||
cp_r "foreman-#{version}.pkg", t.name
|
cp_r "foreman-#{version}.pkg", t.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
6
dist/resources/pkg/Distribution.erb
vendored
6
dist/resources/pkg/Distribution.erb
vendored
@@ -10,14 +10,14 @@
|
|||||||
]]></script>
|
]]></script>
|
||||||
<choices-outline>
|
<choices-outline>
|
||||||
<line choice="git"/>
|
<line choice="git"/>
|
||||||
<line choice="foreman-<%= version %>"/>
|
<line choice="foreman"/>
|
||||||
</choices-outline>
|
</choices-outline>
|
||||||
<choice id="git" title="git" start_selected="false" start_enabled="false" selected="needs_git()" enabled="needs_git()">
|
<choice id="git" title="git" start_selected="false" start_enabled="false" selected="needs_git()" enabled="needs_git()">
|
||||||
<pkg-ref id="git.pkg" />
|
<pkg-ref id="git.pkg" />
|
||||||
</choice>
|
</choice>
|
||||||
<choice id="foreman-<%= version %>" title="foreman">
|
<choice id="foreman" title="foreman">
|
||||||
<pkg-ref id="io.foreman.installer"/>
|
<pkg-ref id="io.foreman.installer"/>
|
||||||
</choice>
|
</choice>
|
||||||
<pkg-ref id="io.foreman.installer" installKBytes="<%= kbytes %>" version="<%= version %>" auth="Root">#foreman-<%= version %>.pkg</pkg-ref>
|
<pkg-ref id="io.foreman.installer" installKBytes="<%= kbytes %>" version="<%= version %>" auth="Root">#foreman.pkg</pkg-ref>
|
||||||
</installer-script>
|
</installer-script>
|
||||||
|
|
||||||
|
|||||||
1
dist/resources/pkg/PackageInfo.erb
vendored
1
dist/resources/pkg/PackageInfo.erb
vendored
@@ -4,4 +4,3 @@
|
|||||||
<postinstall file="./postinstall"/>
|
<postinstall file="./postinstall"/>
|
||||||
</scripts>
|
</scripts>
|
||||||
</pkg-info>
|
</pkg-info>
|
||||||
|
|
||||||
|
|||||||
2
dist/tgz.rake
vendored
2
dist/tgz.rake
vendored
@@ -3,7 +3,7 @@ file pkg("foreman-#{version}.tgz") => distribution_files do |t|
|
|||||||
mkchdir("foreman") do
|
mkchdir("foreman") do
|
||||||
assemble_distribution
|
assemble_distribution
|
||||||
assemble_gems
|
assemble_gems
|
||||||
rm_rf "bin"
|
rm_f "bin/foreman"
|
||||||
assemble resource("tgz/foreman"), "foreman", 0755
|
assemble resource("tgz/foreman"), "foreman", 0755
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ Gem::Specification.new do |gem|
|
|||||||
gem.files = Dir["**/*"].select { |d| d =~ %r{^(README|bin/|data/|ext/|lib/|spec/|test/)} }
|
gem.files = Dir["**/*"].select { |d| d =~ %r{^(README|bin/|data/|ext/|lib/|spec/|test/)} }
|
||||||
gem.files << "man/foreman.1"
|
gem.files << "man/foreman.1"
|
||||||
|
|
||||||
gem.add_dependency 'term-ansicolor', '~> 1.0.7'
|
gem.add_dependency 'thor', '>= 0.13.6'
|
||||||
gem.add_dependency 'thor', '>= 0.13.6'
|
|
||||||
|
|
||||||
if ENV["PLATFORM"] == "java"
|
if ENV["PLATFORM"] == "java"
|
||||||
gem.add_dependency "posix-spawn", "~> 0.3.6"
|
gem.add_dependency "posix-spawn", "~> 0.3.6"
|
||||||
|
|||||||
@@ -4,14 +4,8 @@ module Foreman
|
|||||||
|
|
||||||
class AppDoesNotExist < Exception; end
|
class AppDoesNotExist < Exception; end
|
||||||
|
|
||||||
# load contents of env_file into ENV
|
|
||||||
def self.load_env!(env_file = './.env')
|
|
||||||
require 'foreman/engine'
|
|
||||||
Foreman::Engine.load_env!(env_file)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.runner
|
def self.runner
|
||||||
File.expand_path("../../bin/runner", __FILE__)
|
File.expand_path("../../bin/foreman-runner", __FILE__)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.jruby?
|
def self.jruby?
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
require "foreman"
|
require "foreman"
|
||||||
require "foreman/helpers"
|
require "foreman/helpers"
|
||||||
require "foreman/engine"
|
require "foreman/engine"
|
||||||
|
require "foreman/engine/cli"
|
||||||
require "foreman/export"
|
require "foreman/export"
|
||||||
|
require "shellwords"
|
||||||
require "thor"
|
require "thor"
|
||||||
require "yaml"
|
|
||||||
|
|
||||||
class Foreman::CLI < Thor
|
class Foreman::CLI < Thor
|
||||||
|
|
||||||
include Foreman::Helpers
|
include Foreman::Helpers
|
||||||
|
|
||||||
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
||||||
|
class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
|
||||||
|
|
||||||
desc "start", "Start the application"
|
desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
|
||||||
|
|
||||||
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
method_option :color, :type => :boolean, :aliases => "-c", :desc => "Force color to be enabled"
|
||||||
class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
|
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 :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
method_option :port, :type => :numeric, :aliases => "-p"
|
||||||
method_option :port, :type => :numeric, :aliases => "-p"
|
|
||||||
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Hackery. Take the run method away from Thor so that we can redefine it.
|
# Hackery. Take the run method away from Thor so that we can redefine it.
|
||||||
def is_thor_reserved_word?(word, type)
|
def is_thor_reserved_word?(word, type)
|
||||||
return false if word == 'run'
|
return false if word == "run"
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start(process=nil)
|
||||||
check_procfile!
|
check_procfile!
|
||||||
|
load_environment!
|
||||||
|
engine.load_procfile(procfile)
|
||||||
|
engine.options[:formation] = "#{process}=1" if process
|
||||||
engine.start
|
engine.start
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -44,6 +48,8 @@ class Foreman::CLI < Thor
|
|||||||
|
|
||||||
def export(format, location=nil)
|
def export(format, location=nil)
|
||||||
check_procfile!
|
check_procfile!
|
||||||
|
load_environment!
|
||||||
|
engine.load_procfile(procfile)
|
||||||
formatter = Foreman::Export.formatter(format)
|
formatter = Foreman::Export.formatter(format)
|
||||||
formatter.new(location, engine, options).export
|
formatter.new(location, engine, options).export
|
||||||
rescue Foreman::Export::Exception => ex
|
rescue Foreman::Export::Exception => ex
|
||||||
@@ -53,16 +59,20 @@ class Foreman::CLI < Thor
|
|||||||
desc "check", "Validate your application's Procfile"
|
desc "check", "Validate your application's Procfile"
|
||||||
|
|
||||||
def check
|
def check
|
||||||
error "no processes defined" unless engine.procfile.entries.length > 0
|
check_procfile!
|
||||||
puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
engine.load_procfile(procfile)
|
||||||
|
error "no processes defined" unless engine.processes.length > 0
|
||||||
|
puts "valid procfile detected (#{engine.process_names.join(', ')})"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "run COMMAND", "Run a command using your application's environment"
|
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)
|
def run(*args)
|
||||||
engine.apply_environment!
|
load_environment!
|
||||||
begin
|
begin
|
||||||
exec args.join(" ")
|
exec engine.env, args.shelljoin
|
||||||
rescue Errno::EACCES
|
rescue Errno::EACCES
|
||||||
error "not executable: #{args.first}"
|
error "not executable: #{args.first}"
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
@@ -70,23 +80,44 @@ class Foreman::CLI < Thor
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
no_tasks do
|
||||||
|
def engine
|
||||||
|
@engine ||= begin
|
||||||
|
engine_class = Foreman::Engine::CLI
|
||||||
|
engine = engine_class.new(options)
|
||||||
|
engine
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private ######################################################################
|
private ######################################################################
|
||||||
|
|
||||||
|
def error(message)
|
||||||
|
puts "ERROR: #{message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
def check_procfile!
|
def check_procfile!
|
||||||
error("#{procfile} does not exist.") unless File.exist?(procfile)
|
error("#{procfile} does not exist.") unless File.exist?(procfile)
|
||||||
end
|
end
|
||||||
|
|
||||||
def engine
|
def load_environment!
|
||||||
@engine ||= Foreman::Engine.new(procfile, options)
|
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
|
end
|
||||||
|
|
||||||
def procfile
|
def procfile
|
||||||
options[:procfile] || "Procfile"
|
case
|
||||||
end
|
when options[:procfile] then options[:procfile]
|
||||||
|
when options[:root] then File.expand_path(File.join(options[:app_root], "Procfile"))
|
||||||
def error(message)
|
else "Procfile"
|
||||||
puts "ERROR: #{message}"
|
end
|
||||||
exit 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def options
|
def options
|
||||||
@@ -95,4 +126,5 @@ private ######################################################################
|
|||||||
defaults = YAML::load_file(".foreman") || {}
|
defaults = YAML::load_file(".foreman") || {}
|
||||||
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,116 +1,260 @@
|
|||||||
require "foreman"
|
require "foreman"
|
||||||
|
require "foreman/env"
|
||||||
require "foreman/process"
|
require "foreman/process"
|
||||||
require "foreman/procfile"
|
require "foreman/procfile"
|
||||||
require "foreman/utils"
|
|
||||||
require "tempfile"
|
require "tempfile"
|
||||||
require "timeout"
|
require "timeout"
|
||||||
require "term/ansicolor"
|
|
||||||
require "fileutils"
|
require "fileutils"
|
||||||
require "thread"
|
require "thread"
|
||||||
|
|
||||||
class Foreman::Engine
|
class Foreman::Engine
|
||||||
|
|
||||||
attr_reader :procfile
|
attr_reader :env
|
||||||
attr_reader :directory
|
|
||||||
attr_reader :options
|
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, blue,
|
@options[:formation] ||= "all=1"
|
||||||
intense_cyan, intense_yellow, intense_green, intense_magenta,
|
|
||||||
intense_red, intense_blue ]
|
|
||||||
|
|
||||||
def initialize(procfile, options={})
|
@env = {}
|
||||||
@procfile = Foreman::Procfile.new(procfile)
|
@mutex = Mutex.new
|
||||||
@directory = options[:app_root] || File.expand_path(File.dirname(procfile))
|
@names = {}
|
||||||
@options = options
|
@processes = []
|
||||||
@environment = read_environment_files(options[:env])
|
@running = {}
|
||||||
@output_mutex = Mutex.new
|
@readers = {}
|
||||||
end
|
|
||||||
|
|
||||||
def self.load_env!(env_file)
|
|
||||||
@environment = read_environment_files(env_file)
|
|
||||||
apply_environment!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Start the processes registered to this +Engine+
|
||||||
|
#
|
||||||
def start
|
def start
|
||||||
proctitle "ruby: foreman master"
|
|
||||||
termtitle "#{File.basename(@directory)} - foreman"
|
|
||||||
|
|
||||||
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
||||||
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
||||||
|
trap("HUP") { puts "SIGHUP received"; terminate_gracefully }
|
||||||
|
|
||||||
assign_colors
|
startup
|
||||||
spawn_processes
|
spawn_processes
|
||||||
watch_for_output
|
watch_for_output
|
||||||
watch_for_termination
|
sleep 0.1
|
||||||
|
watch_for_termination { terminate_gracefully }
|
||||||
|
shutdown
|
||||||
end
|
end
|
||||||
|
|
||||||
def port_for(process, num, base_port=nil)
|
# Register a process to be run by this +Engine+
|
||||||
base_port ||= 5000
|
#
|
||||||
offset = procfile.process_names.index(process.name) * 100
|
# @param [String] name A name for this process
|
||||||
base_port.to_i + offset + num - 1
|
# @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
|
end
|
||||||
|
|
||||||
private ######################################################################
|
# Clear the processes registered to this +Engine+
|
||||||
|
#
|
||||||
|
def clear
|
||||||
|
@names = {}
|
||||||
|
@processes = []
|
||||||
|
end
|
||||||
|
|
||||||
def spawn_processes
|
# Register processes by reading a Procfile
|
||||||
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
|
#
|
||||||
|
# @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
|
||||||
|
|
||||||
procfile.entries.each do |entry|
|
# Load a .env file into the +env+ for this +Engine+
|
||||||
reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY"))
|
#
|
||||||
entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
|
# @param [String] filename A .env file to load into the environment
|
||||||
running_processes[process.pid] = process
|
#
|
||||||
readers[process] = reader
|
def load_env(filename)
|
||||||
|
Foreman::Env.new(filename).entries do |name, value|
|
||||||
|
@env[name] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Send a signal to all processesstarted by this +Engine+
|
||||||
|
#
|
||||||
|
# @param [String] signal The signal to send to each process
|
||||||
|
#
|
||||||
|
def killall(signal="SIGTERM")
|
||||||
|
@running.each do |pid, (process, index)|
|
||||||
|
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
|
||||||
|
begin
|
||||||
|
Process.kill(signal, -1 * pid)
|
||||||
|
rescue Errno::ESRCH, Errno::EPERM
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the process formation
|
||||||
|
#
|
||||||
|
# @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_port + (@processes.index(process) * 100) + (instance - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
### Engine API ######################################################
|
||||||
|
|
||||||
|
def startup
|
||||||
|
raise TypeError, "must use a subclass of Foreman::Engine"
|
||||||
|
end
|
||||||
|
|
||||||
|
def output(name, data)
|
||||||
|
raise TypeError, "must use a subclass of Foreman::Engine"
|
||||||
|
end
|
||||||
|
|
||||||
|
def shutdown
|
||||||
|
raise TypeError, "must use a subclass of Foreman::Engine"
|
||||||
|
end
|
||||||
|
|
||||||
|
## Helpers ##########################################################
|
||||||
|
|
||||||
def base_port
|
def base_port
|
||||||
options[:port] || 5000
|
(options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
def kill_all(signal="SIGTERM")
|
def create_pipe
|
||||||
running_processes.each do |pid, process|
|
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
|
||||||
info "sending #{signal} to pid #{pid}"
|
end
|
||||||
process.kill signal
|
|
||||||
|
def name_for(pid)
|
||||||
|
process, index = @running[pid]
|
||||||
|
[ @names[process], index.to_s ].compact.join(".")
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_formation(formation)
|
||||||
|
pairs = @options[:formation].to_s.gsub(/\s/, "").split(",")
|
||||||
|
|
||||||
|
pairs.inject(Hash.new(0)) do |ax, pair|
|
||||||
|
process, amount = pair.split("=")
|
||||||
|
process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
|
||||||
|
ax
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def terminate_gracefully
|
def output_with_mutex(name, message)
|
||||||
return if @terminating
|
@mutex.synchronize do
|
||||||
@terminating = true
|
output name, message
|
||||||
info "sending SIGTERM to all processes"
|
end
|
||||||
kill_all "SIGTERM"
|
end
|
||||||
Timeout.timeout(5) do
|
|
||||||
while running_processes.length > 0
|
def system(message)
|
||||||
pid, status = Process.wait2
|
output_with_mutex "system", message
|
||||||
process = running_processes.delete(pid)
|
end
|
||||||
info "process terminated", process.name
|
|
||||||
|
def termination_message_for(status)
|
||||||
|
if status.exited?
|
||||||
|
"exited with code #{status.exitstatus}"
|
||||||
|
elsif status.signaled?
|
||||||
|
"terminated by SIG#{Signal.list.invert[status.termsig]}"
|
||||||
|
else
|
||||||
|
"died a mysterious death"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def flush_reader(reader)
|
||||||
|
until reader.eof?
|
||||||
|
data = reader.gets
|
||||||
|
output_with_mutex name_for(@readers.key(reader)), data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
## Engine ###########################################################
|
||||||
|
|
||||||
|
def spawn_processes
|
||||||
|
@processes.each do |process|
|
||||||
|
1.upto(formation[@names[process]]) do |n|
|
||||||
|
reader, writer = create_pipe
|
||||||
|
begin
|
||||||
|
pid = process.run(:output => writer, :env => { "PORT" => port_for(process, n).to_s })
|
||||||
|
writer.puts "started with pid #{pid}"
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
writer.puts "unknown command: #{process.command}"
|
||||||
|
end
|
||||||
|
@running[pid] = [process, n]
|
||||||
|
@readers[pid] = reader
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue Timeout::Error
|
|
||||||
info "sending SIGKILL to all processes"
|
|
||||||
kill_all "SIGKILL"
|
|
||||||
end
|
|
||||||
|
|
||||||
def poll_readers
|
|
||||||
rs, ws = IO.select(readers.values, [], [], 1)
|
|
||||||
(rs || []).each do |r|
|
|
||||||
data = r.gets
|
|
||||||
next unless data
|
|
||||||
ps, message = data.split(",", 2)
|
|
||||||
color = colors[ps.split(".").first]
|
|
||||||
info message, ps, color
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def watch_for_output
|
def watch_for_output
|
||||||
Thread.new do
|
Thread.new do
|
||||||
require "win32console" if Foreman.windows?
|
|
||||||
begin
|
begin
|
||||||
loop do
|
loop do
|
||||||
poll_readers
|
(IO.select(@readers.values).first || []).each do |reader|
|
||||||
|
data = reader.gets
|
||||||
|
output_with_mutex name_for(@readers.key(reader)), data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
rescue Exception => ex
|
rescue Exception => ex
|
||||||
puts ex.message
|
puts ex.message
|
||||||
@@ -121,118 +265,24 @@ private ######################################################################
|
|||||||
|
|
||||||
def watch_for_termination
|
def watch_for_termination
|
||||||
pid, status = Process.wait2
|
pid, status = Process.wait2
|
||||||
process = running_processes.delete(pid)
|
output_with_mutex name_for(pid), termination_message_for(status)
|
||||||
info "process terminated", process.name
|
@running.delete(pid)
|
||||||
terminate_gracefully
|
yield if block_given?
|
||||||
|
pid
|
||||||
rescue Errno::ECHILD
|
rescue Errno::ECHILD
|
||||||
end
|
end
|
||||||
|
|
||||||
def info(message, name="system", color=Term::ANSIColor.white)
|
def terminate_gracefully
|
||||||
output = ""
|
return if @terminating
|
||||||
output += color
|
@terminating = true
|
||||||
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
system "sending SIGTERM to all processes"
|
||||||
output += Term::ANSIColor.reset
|
killall "SIGTERM"
|
||||||
output += message.chomp
|
Timeout.timeout(5) do
|
||||||
puts output
|
watch_for_termination while @running.length > 0
|
||||||
end
|
|
||||||
|
|
||||||
def print(message=nil)
|
|
||||||
@output_mutex.synchronize do
|
|
||||||
$stdout.print message
|
|
||||||
end
|
end
|
||||||
|
rescue Timeout::Error
|
||||||
|
system "sending SIGKILL to all processes"
|
||||||
|
killall "SIGKILL"
|
||||||
end
|
end
|
||||||
|
|
||||||
def puts(message=nil)
|
|
||||||
@output_mutex.synchronize do
|
|
||||||
$stdout.puts message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def longest_process_name
|
|
||||||
@longest_process_name ||= begin
|
|
||||||
longest = procfile.process_names.map { |name| name.length }.sort.last
|
|
||||||
longest = 6 if longest < 6 # system
|
|
||||||
longest
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def pad_process_name(name="system")
|
|
||||||
name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
|
|
||||||
end
|
|
||||||
|
|
||||||
def proctitle(title)
|
|
||||||
$0 = title
|
|
||||||
end
|
|
||||||
|
|
||||||
def termtitle(title)
|
|
||||||
printf("\033]0;#{title}\007") unless Foreman.windows?
|
|
||||||
end
|
|
||||||
|
|
||||||
def running_processes
|
|
||||||
@running_processes ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def readers
|
|
||||||
@readers ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def colors
|
|
||||||
@colors ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def assign_colors
|
|
||||||
procfile.entries.each do |entry|
|
|
||||||
colors[entry.name] = next_color
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_by_reader(reader)
|
|
||||||
readers.invert[reader]
|
|
||||||
end
|
|
||||||
|
|
||||||
def next_color
|
|
||||||
@current_color ||= -1
|
|
||||||
@current_color += 1
|
|
||||||
@current_color = 0 if COLORS.length < @current_color
|
|
||||||
COLORS[@current_color]
|
|
||||||
end
|
|
||||||
|
|
||||||
module Env
|
|
||||||
attr_reader :environment
|
|
||||||
|
|
||||||
def read_environment_files(filenames)
|
|
||||||
environment = {}
|
|
||||||
|
|
||||||
(filenames || "").split(",").map(&:strip).each do |filename|
|
|
||||||
error "No such file: #{filename}" unless File.exists?(filename)
|
|
||||||
environment.merge!(read_environment(filename))
|
|
||||||
end
|
|
||||||
|
|
||||||
environment.merge!(read_environment(".env")) unless filenames
|
|
||||||
environment
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_environment(filename)
|
|
||||||
return {} unless File.exists?(filename)
|
|
||||||
|
|
||||||
File.read(filename).split("\n").inject({}) do |hash, line|
|
|
||||||
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
|
|
||||||
hash[$1] = $2
|
|
||||||
end
|
|
||||||
hash
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply_environment!
|
|
||||||
@environment.each { |k,v| ENV[k] = v }
|
|
||||||
end
|
|
||||||
|
|
||||||
def error(message)
|
|
||||||
puts "ERROR: #{message}"
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
include Env
|
|
||||||
extend Env
|
|
||||||
end
|
end
|
||||||
|
|||||||
103
lib/foreman/engine/cli.rb
Normal file
103
lib/foreman/engine/cli.rb
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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 unless self.respond_to?(:isatty)
|
||||||
|
self.isatty && ENV["TERM"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def color(name)
|
||||||
|
return "" unless color?
|
||||||
|
return "" unless ansi = ANSI[name.to_sym]
|
||||||
|
"\e[#{ansi}m"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
FOREMAN_COLORS = %w( cyan yellow green magenta red blue intense_cyan intense_yellow
|
||||||
|
intense_green intense_magenta intense_red, intense_blue )
|
||||||
|
|
||||||
|
def startup
|
||||||
|
@colors = map_colors
|
||||||
|
proctitle "foreman: master"
|
||||||
|
end
|
||||||
|
|
||||||
|
def output(name, data)
|
||||||
|
data.to_s.chomp.split("\n").each do |message|
|
||||||
|
Color.enable($stdout, options[:color]) unless $stdout.respond_to?(:color?)
|
||||||
|
output = ""
|
||||||
|
output += $stdout.color(@colors[name.split(".").first].to_sym)
|
||||||
|
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
||||||
|
output += $stdout.color(:reset)
|
||||||
|
output += message
|
||||||
|
$stdout.puts output
|
||||||
|
$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"] = "intense_white"
|
||||||
|
colors
|
||||||
|
end
|
||||||
|
|
||||||
|
def proctitle(title)
|
||||||
|
$0 = title
|
||||||
|
end
|
||||||
|
|
||||||
|
def termtitle(title)
|
||||||
|
printf("\033]0;#{title}\007") unless Foreman.windows?
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
27
lib/foreman/env.rb
Normal file
27
lib/foreman/env.rb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
require "foreman"
|
||||||
|
|
||||||
|
class Foreman::Env
|
||||||
|
|
||||||
|
attr_reader :entries
|
||||||
|
|
||||||
|
def initialize(filename)
|
||||||
|
@entries = File.read(filename).split("\n").inject({}) do |ax, line|
|
||||||
|
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
|
||||||
|
key = $1
|
||||||
|
case val = $2
|
||||||
|
when /\A'(.*)'\z/ then ax[key] = $1
|
||||||
|
when /\A"(.*)"\z/ then ax[key] = $1.gsub(/\\(.)/, '\1')
|
||||||
|
else ax[key] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ax
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def entries
|
||||||
|
@entries.each do |key, value|
|
||||||
|
yield key, value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -24,9 +24,11 @@ module Foreman::Export
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
require "foreman/export/base"
|
require "foreman/export/base"
|
||||||
require "foreman/export/inittab"
|
require "foreman/export/inittab"
|
||||||
require "foreman/export/upstart"
|
require "foreman/export/upstart"
|
||||||
require "foreman/export/bluepill"
|
require "foreman/export/bluepill"
|
||||||
require "foreman/export/runit"
|
require "foreman/export/runit"
|
||||||
|
require "foreman/export/supervisord"
|
||||||
|
require "foreman/export/launchd"
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
require "foreman/export"
|
require "foreman/export"
|
||||||
require "foreman/utils"
|
require "shellwords"
|
||||||
|
|
||||||
class Foreman::Export::Base
|
class Foreman::Export::Base
|
||||||
|
|
||||||
attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
|
attr_reader :location
|
||||||
|
attr_reader :engine
|
||||||
|
attr_reader :options
|
||||||
|
attr_reader :formation
|
||||||
|
|
||||||
def initialize(location, engine, options={})
|
def initialize(location, engine, options={})
|
||||||
@location = location
|
@location = location
|
||||||
@engine = engine
|
@engine = engine
|
||||||
@app = options[:app]
|
@options = options.dup
|
||||||
@log = options[:log]
|
@formation = engine.formation
|
||||||
@port = options[:port]
|
|
||||||
@user = options[:user]
|
|
||||||
@template = options[:template]
|
|
||||||
@concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def export
|
def export
|
||||||
raise "export method must be overridden"
|
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.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def app
|
||||||
|
options[:app] || "app"
|
||||||
|
end
|
||||||
|
|
||||||
|
def log
|
||||||
|
options[:log] || "/var/log/#{app}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def user
|
||||||
|
options[:user] || app
|
||||||
end
|
end
|
||||||
|
|
||||||
private ######################################################################
|
private ######################################################################
|
||||||
@@ -30,20 +44,44 @@ private ######################################################################
|
|||||||
puts "[foreman export] %s" % message
|
puts "[foreman export] %s" % message
|
||||||
end
|
end
|
||||||
|
|
||||||
def export_template(exporter, file, template_root)
|
def clean(filename)
|
||||||
if template_root && File.exist?(file_path = File.join(template_root, file))
|
return unless File.exists?(filename)
|
||||||
File.read(file_path)
|
say "cleaning up: #{filename}"
|
||||||
elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
|
FileUtils.rm(filename)
|
||||||
File.read(file_path)
|
end
|
||||||
else
|
|
||||||
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
|
def shell_quote(value)
|
||||||
end
|
'"' + Shellwords.escape(value) + '"'
|
||||||
|
end
|
||||||
|
|
||||||
|
def export_template(name)
|
||||||
|
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
|
||||||
|
|
||||||
|
def write_template(name, target, binding)
|
||||||
|
compiled = ERB.new(export_template(name)).result(binding)
|
||||||
|
write_file target, compiled
|
||||||
|
end
|
||||||
|
|
||||||
|
def chmod(mode, file)
|
||||||
|
say "setting #{file} to mode #{mode}"
|
||||||
|
FileUtils.chmod mode, File.join(location, file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_directory(dir)
|
||||||
|
say "creating: #{dir}"
|
||||||
|
FileUtils.mkdir_p(File.join(location, dir))
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_file(filename, contents)
|
def write_file(filename, contents)
|
||||||
say "writing: #{filename}"
|
say "writing: #{filename}"
|
||||||
|
|
||||||
File.open(filename, "w") do |file|
|
File.open(File.join(location, filename), "w") do |file|
|
||||||
file.puts contents
|
file.puts contents
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,23 +4,9 @@ require "foreman/export"
|
|||||||
class Foreman::Export::Bluepill < Foreman::Export::Base
|
class Foreman::Export::Bluepill < Foreman::Export::Base
|
||||||
|
|
||||||
def export
|
def export
|
||||||
error("Must specify a location") unless location
|
super
|
||||||
|
clean "#{location}/#{app}.pill"
|
||||||
FileUtils.mkdir_p location
|
write_template "bluepill/master.pill.erb", "#{app}.pill", binding
|
||||||
|
|
||||||
app = self.app || File.basename(engine.directory)
|
|
||||||
user = self.user || app
|
|
||||||
log_root = self.log || "/var/log/#{app}"
|
|
||||||
template_root = self.template
|
|
||||||
|
|
||||||
Dir["#{location}/#{app}.pill"].each do |file|
|
|
||||||
say "cleaning up: #{file}"
|
|
||||||
FileUtils.rm(file)
|
|
||||||
end
|
|
||||||
|
|
||||||
master_template = export_template("bluepill", "master.pill.erb", template_root)
|
|
||||||
master_config = ERB.new(master_template).result(binding)
|
|
||||||
write_file "#{location}/#{app}.pill", master_config
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,21 +3,19 @@ require "foreman/export"
|
|||||||
class Foreman::Export::Inittab < Foreman::Export::Base
|
class Foreman::Export::Inittab < Foreman::Export::Base
|
||||||
|
|
||||||
def export
|
def export
|
||||||
app = self.app || File.basename(engine.directory)
|
error("Must specify a location") unless location
|
||||||
user = self.user || app
|
|
||||||
log_root = self.log || "/var/log/#{app}"
|
|
||||||
|
|
||||||
inittab = []
|
inittab = []
|
||||||
inittab << "# ----- foreman #{app} processes -----"
|
inittab << "# ----- foreman #{app} processes -----"
|
||||||
|
|
||||||
engine.procfile.entries.inject(1) do |index, process|
|
index = 1
|
||||||
1.upto(self.concurrency[process.name]) do |num|
|
engine.each_process do |name, process|
|
||||||
|
1.upto(engine.formation[name]) do |num|
|
||||||
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
||||||
port = engine.port_for(process, num, self.port)
|
port = engine.port_for(process, num)
|
||||||
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
|
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log}/#{name}-#{num}.log 2>&1'"
|
||||||
index += 1
|
index += 1
|
||||||
end
|
end
|
||||||
index
|
|
||||||
end
|
end
|
||||||
|
|
||||||
inittab << "# ----- end foreman #{app} processes -----"
|
inittab << "# ----- end foreman #{app} processes -----"
|
||||||
@@ -27,9 +25,8 @@ class Foreman::Export::Inittab < Foreman::Export::Base
|
|||||||
if location == "-"
|
if location == "-"
|
||||||
puts inittab
|
puts inittab
|
||||||
else
|
else
|
||||||
FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}"
|
say "writing: #{location}"
|
||||||
FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}"
|
File.open(location, "w") { |file| file.puts inittab }
|
||||||
write_file(location, inittab)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
15
lib/foreman/export/launchd.rb
Normal file
15
lib/foreman/export/launchd.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
require "erb"
|
||||||
|
require "foreman/export"
|
||||||
|
|
||||||
|
class Foreman::Export::Launchd < Foreman::Export::Base
|
||||||
|
|
||||||
|
def export
|
||||||
|
super
|
||||||
|
engine.each_process do |name, process|
|
||||||
|
1.upto(engine.formation[name]) do |num|
|
||||||
|
write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -2,58 +2,33 @@ require "erb"
|
|||||||
require "foreman/export"
|
require "foreman/export"
|
||||||
|
|
||||||
class Foreman::Export::Runit < Foreman::Export::Base
|
class Foreman::Export::Runit < Foreman::Export::Base
|
||||||
|
|
||||||
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
||||||
|
|
||||||
def export
|
def export
|
||||||
error("Must specify a location") unless location
|
super
|
||||||
|
|
||||||
app = self.app || File.basename(engine.directory)
|
engine.each_process do |name, process|
|
||||||
user = self.user || app
|
1.upto(engine.formation[name]) do |num|
|
||||||
log_root = self.log || "/var/log/#{app}"
|
process_directory = "#{app}-#{name}-#{num}"
|
||||||
template_root = self.template
|
|
||||||
|
|
||||||
run_template = export_template('runit', 'run.erb', template_root)
|
|
||||||
log_run_template = export_template('runit', 'log_run.erb', template_root)
|
|
||||||
|
|
||||||
engine.procfile.entries.each do |process|
|
|
||||||
1.upto(self.concurrency[process.name]) do |num|
|
|
||||||
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
|
|
||||||
process_env_directory = "#{process_directory}/env"
|
|
||||||
process_log_directory = "#{process_directory}/log"
|
|
||||||
|
|
||||||
create_directory process_directory
|
create_directory process_directory
|
||||||
create_directory process_env_directory
|
create_directory "#{process_directory}/env"
|
||||||
create_directory process_log_directory
|
create_directory "#{process_directory}/log"
|
||||||
|
|
||||||
run = ERB.new(run_template).result(binding)
|
write_template "runit/run.erb", "#{process_directory}/run", binding
|
||||||
write_file "#{process_directory}/run", run
|
chmod 0755, "#{process_directory}/run"
|
||||||
FileUtils.chmod 0755, "#{process_directory}/run"
|
|
||||||
|
|
||||||
port = engine.port_for(process, num, self.port)
|
port = engine.port_for(process, num)
|
||||||
environment_variables = {'PORT' => port}.
|
engine.env.merge("PORT" => port.to_s).each do |key, value|
|
||||||
merge(engine.environment).
|
write_file "#{process_directory}/env/#{key}", value
|
||||||
merge(inline_variables(process.command))
|
|
||||||
|
|
||||||
environment_variables.each_pair do |var, env|
|
|
||||||
write_file "#{process_env_directory}/#{var.upcase}", env
|
|
||||||
end
|
end
|
||||||
|
|
||||||
log_run = ERB.new(log_run_template).result(binding)
|
write_template "runit/log/run.erb", "#{process_directory}/log/run", binding
|
||||||
write_file "#{process_log_directory}/run", log_run
|
chmod 0755, "#{process_directory}/log/run"
|
||||||
FileUtils.chmod 0755, "#{process_log_directory}/run"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
def create_directory(location)
|
|
||||||
say "creating: #{location}"
|
|
||||||
FileUtils.mkdir_p(location)
|
|
||||||
end
|
|
||||||
|
|
||||||
def inline_variables(command)
|
|
||||||
variable_name_regex =
|
|
||||||
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
16
lib/foreman/export/supervisord.rb
Normal file
16
lib/foreman/export/supervisord.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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
|
||||||
@@ -4,38 +4,22 @@ require "foreman/export"
|
|||||||
class Foreman::Export::Upstart < Foreman::Export::Base
|
class Foreman::Export::Upstart < Foreman::Export::Base
|
||||||
|
|
||||||
def export
|
def export
|
||||||
error("Must specify a location") unless location
|
super
|
||||||
|
|
||||||
FileUtils.mkdir_p location
|
|
||||||
|
|
||||||
app = self.app || File.basename(engine.directory)
|
|
||||||
user = self.user || app
|
|
||||||
log_root = self.log || "/var/log/#{app}"
|
|
||||||
template_root = self.template
|
|
||||||
|
|
||||||
Dir["#{location}/#{app}*.conf"].each do |file|
|
Dir["#{location}/#{app}*.conf"].each do |file|
|
||||||
say "cleaning up: #{file}"
|
clean file
|
||||||
FileUtils.rm(file)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
master_template = export_template("upstart", "master.conf.erb", template_root)
|
write_template "upstart/master.conf.erb", "#{app}.conf", binding
|
||||||
master_config = ERB.new(master_template).result(binding)
|
|
||||||
write_file "#{location}/#{app}.conf", master_config
|
|
||||||
|
|
||||||
process_template = export_template("upstart", "process.conf.erb", template_root)
|
engine.each_process do |name, process|
|
||||||
|
next if engine.formation[name] < 1
|
||||||
|
write_template "upstart/process_master.conf.erb", "#{app}-#{name}.conf", binding
|
||||||
|
|
||||||
engine.procfile.entries.each do |process|
|
1.upto(engine.formation[name]) do |num|
|
||||||
next if (conc = self.concurrency[process.name]) < 1
|
port = engine.port_for(process, num)
|
||||||
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
write_template "upstart/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
|
||||||
process_master_config = ERB.new(process_master_template).result(binding)
|
|
||||||
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
|
||||||
|
|
||||||
1.upto(self.concurrency[process.name]) do |num|
|
|
||||||
port = engine.port_for(process, num, self.port)
|
|
||||||
process_config = ERB.new(process_template).result(binding)
|
|
||||||
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,94 +3,83 @@ require "rubygems"
|
|||||||
|
|
||||||
class Foreman::Process
|
class Foreman::Process
|
||||||
|
|
||||||
attr_reader :entry
|
attr_reader :command
|
||||||
attr_reader :num
|
attr_reader :env
|
||||||
attr_reader :pid
|
|
||||||
attr_reader :port
|
|
||||||
|
|
||||||
def initialize(entry, num, port)
|
# Create a Process
|
||||||
@entry = entry
|
#
|
||||||
@num = num
|
# @param [String] command The command to run
|
||||||
@port = port
|
# @param [Hash] options
|
||||||
|
#
|
||||||
|
# @option options [String] :cwd (./) Change to this working directory before executing the process
|
||||||
|
# @option options [Hash] :env ({}) Environment variables to set for this process
|
||||||
|
#
|
||||||
|
def initialize(command, options={})
|
||||||
|
@command = command
|
||||||
|
@options = options.dup
|
||||||
|
|
||||||
|
@options[:env] ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(pipe, basedir, environment)
|
# Run a +Process+
|
||||||
with_environment(environment.merge("PORT" => port.to_s)) do
|
#
|
||||||
run_process basedir, entry.command, pipe
|
# @param [Hash] options
|
||||||
|
#
|
||||||
|
# @option options :env ({}) Environment variables to set for this execution
|
||||||
|
# @option options :output ($stdout) The output stream
|
||||||
|
#
|
||||||
|
# @returns [Fixnum] pid The +pid+ of the process
|
||||||
|
#
|
||||||
|
def run(options={})
|
||||||
|
env = options[:env] ? @options[:env].merge(options[:env]) : @options[:env]
|
||||||
|
output = options[:output] || $stdout
|
||||||
|
|
||||||
|
if Foreman.windows?
|
||||||
|
Dir.chdir(cwd) do
|
||||||
|
Process.spawn env, command, :out => output, :err => output, :new_pgroup => true
|
||||||
|
end
|
||||||
|
elsif Foreman.jruby?
|
||||||
|
Dir.chdir(cwd) do
|
||||||
|
require "posix/spawn"
|
||||||
|
POSIX::Spawn.spawn env, command, :out => output, :err => output, :pgroup => 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Dir.chdir(cwd) do
|
||||||
|
Process.spawn env, command, :out => output, :err => output, :pgroup => 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
# Send a signal to this +Process+
|
||||||
"%s.%s" % [ entry.name, num ]
|
#
|
||||||
end
|
# @param [String] signal The signal to send
|
||||||
|
#
|
||||||
def kill(signal)
|
def kill(signal)
|
||||||
pid && Process.kill(signal, pid)
|
pid && Process.kill(signal, -1 * pid)
|
||||||
rescue Errno::ESRCH
|
rescue Errno::ESRCH
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def detach
|
# Test whether or not this +Process+ is still running
|
||||||
pid && Process.detach(pid)
|
#
|
||||||
end
|
# @returns [Boolean]
|
||||||
|
#
|
||||||
def alive?
|
def alive?
|
||||||
kill(0)
|
kill(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Test whether or not this +Process+ has terminated
|
||||||
|
#
|
||||||
|
# @returns [Boolean]
|
||||||
|
#
|
||||||
def dead?
|
def dead?
|
||||||
!alive?
|
!alive?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def fork_with_io(command, basedir)
|
def cwd
|
||||||
reader, writer = IO.pipe
|
@options[:cwd] || "."
|
||||||
command = replace_command_env(command)
|
|
||||||
pid = if Foreman.windows?
|
|
||||||
Dir.chdir(basedir) do
|
|
||||||
Process.spawn command, :out => writer, :err => writer
|
|
||||||
end
|
|
||||||
elsif Foreman.jruby?
|
|
||||||
require "posix/spawn"
|
|
||||||
POSIX::Spawn.spawn(Foreman.runner, "-d", basedir, command, {
|
|
||||||
:out => writer, :err => writer
|
|
||||||
})
|
|
||||||
else
|
|
||||||
fork do
|
|
||||||
writer.sync = true
|
|
||||||
$stdout.reopen writer
|
|
||||||
$stderr.reopen writer
|
|
||||||
reader.close
|
|
||||||
exec Foreman.runner, "-d", basedir, command
|
|
||||||
end
|
|
||||||
end
|
|
||||||
[ reader, pid ]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_process(basedir, command, pipe)
|
|
||||||
io, @pid = fork_with_io(command, basedir)
|
|
||||||
output pipe, "started with pid %d" % @pid
|
|
||||||
Thread.new do
|
|
||||||
until io.eof?
|
|
||||||
output pipe, io.gets
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def output(pipe, message)
|
|
||||||
pipe.puts "%s,%s" % [ name, message ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def replace_command_env(command)
|
|
||||||
command.gsub(/\$(\w+)/) { |e| ENV[e[1..-1]] }
|
|
||||||
end
|
|
||||||
|
|
||||||
def with_environment(environment)
|
|
||||||
original = ENV.to_hash
|
|
||||||
ENV.update environment
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
ENV.replace original
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,36 +1,90 @@
|
|||||||
require "foreman"
|
require "foreman"
|
||||||
require "foreman/procfile_entry"
|
|
||||||
|
|
||||||
# A valid Procfile entry is captured by this regex.
|
# Reads and writes Procfiles
|
||||||
|
#
|
||||||
|
# A valid Procfile entry is captured by this regex:
|
||||||
|
#
|
||||||
|
# /^([A-Za-z0-9_]+):\s*(.+)$/
|
||||||
|
#
|
||||||
# All other lines are ignored.
|
# All other lines are ignored.
|
||||||
#
|
#
|
||||||
# /^([A-Za-z0-9_]+):\s*(.+)$/
|
|
||||||
#
|
|
||||||
# $1 = name
|
|
||||||
# $2 = command
|
|
||||||
#
|
|
||||||
class Foreman::Procfile
|
class Foreman::Procfile
|
||||||
|
|
||||||
attr_reader :entries
|
# Initialize a Procfile
|
||||||
|
#
|
||||||
def initialize(filename)
|
# @param [String] filename (nil) An optional filename to read from
|
||||||
@entries = parse_procfile(filename)
|
#
|
||||||
|
def initialize(filename=nil)
|
||||||
|
@entries = []
|
||||||
|
load(filename) if filename
|
||||||
end
|
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)
|
def [](name)
|
||||||
entries.detect { |entry| entry.name == name }
|
@entries.detect { |n,c| name == n }.last
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_names
|
# Create a +Procfile+ entry
|
||||||
entries.map(&:name)
|
#
|
||||||
|
# @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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parse_procfile(filename)
|
def parse(filename)
|
||||||
File.read(filename).split("\n").map do |line|
|
File.read(filename).split("\n").map do |line|
|
||||||
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
|
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
|
||||||
Foreman::ProcfileEntry.new($1, $2)
|
[$1, $2]
|
||||||
end
|
end
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
require "foreman"
|
|
||||||
|
|
||||||
class Foreman::ProcfileEntry
|
|
||||||
|
|
||||||
attr_reader :name
|
|
||||||
attr_reader :command
|
|
||||||
attr_accessor :color
|
|
||||||
|
|
||||||
def initialize(name, command)
|
|
||||||
@name = name
|
|
||||||
@command = command
|
|
||||||
end
|
|
||||||
|
|
||||||
def spawn(num, pipe, basedir, environment, base_port)
|
|
||||||
(1..num).to_a.map do |n|
|
|
||||||
process = Foreman::Process.new(self, n, base_port + (n-1))
|
|
||||||
process.run(pipe, basedir, environment)
|
|
||||||
process
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
require "foreman"
|
|
||||||
|
|
||||||
class Foreman::Utils
|
|
||||||
|
|
||||||
def self.parse_concurrency(concurrency)
|
|
||||||
begin
|
|
||||||
pairs = concurrency.to_s.gsub(/\s/, "").split(",")
|
|
||||||
|
|
||||||
default = concurrency.nil? ? 1 : 0
|
|
||||||
|
|
||||||
pairs.inject(Hash.new(default)) do |hash, pair|
|
|
||||||
process, amount = pair.split("=")
|
|
||||||
hash.update(process => amount.to_i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
module Foreman
|
module Foreman
|
||||||
|
|
||||||
VERSION = "0.37.2"
|
VERSION = "0.48.0.pre2"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.\" generated with Ronn/v0.7.3
|
.\" generated with Ronn/v0.7.3
|
||||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||||
.
|
.
|
||||||
.TH "FOREMAN" "1" "January 2012" "Foreman 0.37.2" "Foreman Manual"
|
.TH "FOREMAN" "1" "April 2012" "Foreman 0.46.0" "Foreman Manual"
|
||||||
.
|
.
|
||||||
.SH "NAME"
|
.SH "NAME"
|
||||||
\fBforeman\fR \- manage Procfile\-based applications
|
\fBforeman\fR \- manage Procfile\-based applications
|
||||||
@@ -10,10 +10,13 @@
|
|||||||
\fBforeman start [process]\fR
|
\fBforeman start [process]\fR
|
||||||
.
|
.
|
||||||
.br
|
.br
|
||||||
|
\fBforeman run <command>\fR
|
||||||
|
.
|
||||||
|
.br
|
||||||
\fBforeman export <format> [location]\fR
|
\fBforeman export <format> [location]\fR
|
||||||
.
|
.
|
||||||
.SH "DESCRIPTION"
|
.SH "DESCRIPTION"
|
||||||
\fBForeman\fR 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\.
|
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"
|
.SH "RUNNING"
|
||||||
\fBforeman start\fR is used to run your application directly from the command line\.
|
\fBforeman start\fR is used to run your application directly from the command line\.
|
||||||
@@ -32,9 +35,24 @@ 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 \fBprocess=num,process=num\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
|
.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
|
\fB\-p\fR, \fB\-\-port\fR
|
||||||
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
||||||
.
|
.
|
||||||
|
.TP
|
||||||
|
\fB\-t\fR, \fB\-\-tmux\fR
|
||||||
|
Runs the processes in a tmux session\. Creates one window for each process and an extra window containing the output of each window (requires gawk)\.
|
||||||
|
.
|
||||||
|
.P
|
||||||
|
\fBforeman run\fR is used to run one\-off commands using the same environment as your defined processes\.
|
||||||
|
.
|
||||||
.SH "EXPORTING"
|
.SH "EXPORTING"
|
||||||
\fBforeman export\fR is used to export your application to another process management format\.
|
\fBforeman export\fR is used to export your application to another process management format\.
|
||||||
.
|
.
|
||||||
@@ -61,10 +79,14 @@ Specify the directory to place process logs in\.
|
|||||||
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
||||||
.
|
.
|
||||||
.TP
|
.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
|
\fB\-u\fR, \fB\-\-user\fR
|
||||||
Specify the user the application should be run as\. Defaults to the app name
|
Specify the user the application should be run as\. Defaults to the app name
|
||||||
.
|
.
|
||||||
.SH "OPTIONS"
|
.SH "GLOBAL OPTIONS"
|
||||||
These options control all modes of foreman\'s operation\.
|
These options control all modes of foreman\'s operation\.
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ foreman(1) -- manage Procfile-based applications
|
|||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
|
|
||||||
`foreman start [process]`<br>
|
`foreman start [process]`<br>
|
||||||
|
`foreman run <command>`<br>
|
||||||
`foreman export <format> [location]`
|
`foreman export <format> [location]`
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
|
|
||||||
**Foreman** is a manager for Procfile-based applications. Its aim is to
|
Foreman is a manager for Procfile-based applications. Its aim is to
|
||||||
abstract away the details of the Procfile format, and allow you to either run
|
abstract away the details of the Procfile format, and allow you to either run
|
||||||
your application directly or export it to some other process management
|
your application directly or export it to some other process management
|
||||||
format.
|
format.
|
||||||
@@ -29,10 +30,23 @@ The following options control how the application is run:
|
|||||||
Specify the number of each process type to run. The value passed in
|
Specify the number of each process type to run. The value passed in
|
||||||
should be in the format `process=num,process=num`
|
should be in the format `process=num,process=num`
|
||||||
|
|
||||||
|
* `-e`, `--env`:
|
||||||
|
Specify one or more .env files to load
|
||||||
|
|
||||||
|
* `-f`, `--procfile`:
|
||||||
|
Specify an alternate Procfile to load, implies `-d` at the Procfile root.
|
||||||
|
|
||||||
* `-p`, `--port`:
|
* `-p`, `--port`:
|
||||||
Specify which port to use as the base for this application. Should be
|
Specify which port to use as the base for this application. Should be
|
||||||
a multiple of 1000.
|
a multiple of 1000.
|
||||||
|
|
||||||
|
* `-t`, `--tmux`:
|
||||||
|
Runs the processes in a tmux session. Creates one window for each process
|
||||||
|
and an extra window containing the output of each window (requires gawk).
|
||||||
|
|
||||||
|
`foreman run` is used to run one-off commands using the same environment
|
||||||
|
as your defined processes.
|
||||||
|
|
||||||
## EXPORTING
|
## EXPORTING
|
||||||
|
|
||||||
`foreman export` is used to export your application to another process
|
`foreman export` is used to export your application to another process
|
||||||
@@ -58,11 +72,15 @@ The following options control how the application is run:
|
|||||||
Specify which port to use as the base for this application. Should be
|
Specify which port to use as the base for this application. Should be
|
||||||
a multiple of 1000.
|
a multiple of 1000.
|
||||||
|
|
||||||
|
* `-t`, `--template`:
|
||||||
|
Specify an alternate template to use for creating export files.
|
||||||
|
See <https://github.com/ddollar/foreman/tree/master/data/export> for examples.
|
||||||
|
|
||||||
* `-u`, `--user`:
|
* `-u`, `--user`:
|
||||||
Specify the user the application should be run as. Defaults to the
|
Specify the user the application should be run as. Defaults to the
|
||||||
app name
|
app name
|
||||||
|
|
||||||
## OPTIONS
|
## GLOBAL OPTIONS
|
||||||
|
|
||||||
These options control all modes of foreman's operation.
|
These options control all modes of foreman's operation.
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,22 @@ require "foreman/cli"
|
|||||||
describe "Foreman::CLI", :fakefs do
|
describe "Foreman::CLI", :fakefs do
|
||||||
subject { Foreman::CLI.new }
|
subject { Foreman::CLI.new }
|
||||||
|
|
||||||
|
describe ".foreman" do
|
||||||
|
before { File.open(".foreman", "w") { |f| f.puts "formation: alpha=2" } }
|
||||||
|
|
||||||
|
it "provides default options" do
|
||||||
|
subject.send(:options)["formation"].should == "alpha=2"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is overridden by options at the cli" do
|
||||||
|
subject = Foreman::CLI.new([], :formation => "alpha=3")
|
||||||
|
subject.send(:options)["formation"].should == "alpha=3"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "start" do
|
describe "start" do
|
||||||
describe "with a non-existent Procfile" do
|
describe "when a Procfile doesnt exist", :fakefs do
|
||||||
it "prints an error" do
|
it "displays an error" do
|
||||||
mock_error(subject, "Procfile does not exist.") do
|
mock_error(subject, "Procfile does not exist.") do
|
||||||
dont_allow.instance_of(Foreman::Engine).start
|
dont_allow.instance_of(Foreman::Engine).start
|
||||||
subject.start
|
subject.start
|
||||||
@@ -14,149 +27,50 @@ describe "Foreman::CLI", :fakefs do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with a Procfile" do
|
describe "with a valid Procfile" do
|
||||||
before(:each) { write_procfile }
|
it "can run a single command" do
|
||||||
|
without_fakefs do
|
||||||
it "runs successfully" do
|
output = foreman("start env -f #{resource_path("Procfile")}")
|
||||||
dont_allow(subject).error
|
output.should =~ /env.1/
|
||||||
mock.instance_of(Foreman::Engine).start
|
output.should_not =~ /test.1/
|
||||||
subject.start
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "export" do
|
|
||||||
describe "options" do
|
|
||||||
it "uses .foreman" do
|
|
||||||
write_procfile
|
|
||||||
File.open(".foreman", "w") { |f| f.puts "concurrency: alpha=2" }
|
|
||||||
mock_export = mock(Foreman::Export::Upstart)
|
|
||||||
mock(Foreman::Export::Upstart).new("/upstart", is_a(Foreman::Engine), { "concurrency" => "alpha=2" }) { mock_export }
|
|
||||||
mock_export.export
|
|
||||||
foreman %{ export upstart /upstart }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "respects --env" do
|
|
||||||
write_procfile
|
|
||||||
write_env("envfile")
|
|
||||||
mock_export = mock(Foreman::Export::Upstart)
|
|
||||||
mock(Foreman::Export::Upstart).new("/upstart", is_a(Foreman::Engine), { "env" => "envfile" }) { mock_export }
|
|
||||||
mock_export.export
|
|
||||||
foreman %{ export upstart /upstart --env envfile }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
describe "with a Procfile" do
|
|
||||||
before(:each) { write_procfile }
|
|
||||||
|
|
||||||
describe "with a formatter with a generic error" do
|
|
||||||
before do
|
|
||||||
mock(Foreman::Export).formatter("errorful") { Class.new(Foreman::Export::Base) do
|
|
||||||
def export
|
|
||||||
raise Foreman::Export::Exception.new("foo")
|
|
||||||
end
|
|
||||||
end }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "prints an error" do
|
|
||||||
mock_error(subject, "foo") do
|
|
||||||
subject.export("errorful")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with a valid config" do
|
it "can run all commands" do
|
||||||
before(:each) { write_foreman_config("testapp") }
|
without_fakefs do
|
||||||
|
output = foreman("start -f #{resource_path("Procfile")} -e #{resource_path(".env")}")
|
||||||
it "runs successfully" do
|
output.should =~ /echo.1 \| echoing/
|
||||||
dont_allow(subject).error
|
output.should =~ /env.1 \| bar/
|
||||||
mock_export = mock(Foreman::Export::Upstart)
|
output.should =~ /test.1 \| testing/
|
||||||
mock(Foreman::Export::Upstart).new("/tmp/foo", is_a(Foreman::Engine), {}) { mock_export }
|
|
||||||
mock_export.export
|
|
||||||
subject.export("upstart", "/tmp/foo")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "check" do
|
describe "check" do
|
||||||
describe "with a valid Procfile" do
|
it "with a valid Procfile displays the jobs" do
|
||||||
before { write_procfile }
|
write_procfile
|
||||||
|
foreman("check").should == "valid procfile detected (alpha, bravo)\n"
|
||||||
it "displays the jobs" do
|
|
||||||
mock(subject).puts("valid procfile detected (alpha, bravo)")
|
|
||||||
subject.check
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with a blank Procfile" do
|
it "with a blank Procfile displays an error" do
|
||||||
before do
|
FileUtils.touch "Procfile"
|
||||||
FileUtils.touch("Procfile")
|
foreman("check").should == "ERROR: no processes defined\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "displays an error" do
|
it "without a Procfile displays an error" do
|
||||||
mock_error(subject, "no processes defined") do
|
FileUtils.rm_f "Procfile"
|
||||||
subject.check
|
foreman("check").should == "ERROR: Procfile does not exist.\n"
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "run" do
|
describe "run" do
|
||||||
describe "with a valid Procfile" do
|
it "can run a command" do
|
||||||
before { write_procfile }
|
forked_foreman("run echo 1").should == "1\n"
|
||||||
|
end
|
||||||
|
|
||||||
describe "and a command" do
|
it "includes the environment" do
|
||||||
let(:command) { ["ls", "-l"] }
|
forked_foreman("run #{resource_path("bin/env FOO")} -e #{resource_path(".env")}").should == "bar\n"
|
||||||
|
|
||||||
before(:each) do
|
|
||||||
stub(subject).exec
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should load the environment file" do
|
|
||||||
write_env
|
|
||||||
preserving_env do
|
|
||||||
subject.run *command
|
|
||||||
ENV["FOO"].should == "bar"
|
|
||||||
end
|
|
||||||
|
|
||||||
ENV["FOO"].should be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should runute the command as a string" do
|
|
||||||
mock(subject).exec(command.join(" "))
|
|
||||||
subject.run *command
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "and a non-existent command" do
|
|
||||||
let(:command) { "iuhtngrglhulhdfg" }
|
|
||||||
|
|
||||||
it "should print an error" do
|
|
||||||
mock_error(subject, "command not found: #{command}") do
|
|
||||||
subject.run command
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "and a non-executable command" do
|
|
||||||
let(:command) { __FILE__ }
|
|
||||||
|
|
||||||
it "should print an error" do
|
|
||||||
mock_error(subject, "not executable: #{command}") do
|
|
||||||
subject.run command
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +1,104 @@
|
|||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
require "foreman/engine"
|
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
|
describe "Foreman::Engine", :fakefs do
|
||||||
subject { Foreman::Engine.new("Procfile", {}) }
|
subject do
|
||||||
|
write_procfile "Procfile"
|
||||||
|
Foreman::Engine::Tester.new.load_procfile("Procfile")
|
||||||
|
end
|
||||||
|
|
||||||
describe "initialize" do
|
describe "initialize" do
|
||||||
describe "without an existing Procfile" do
|
|
||||||
it "raises an error" do
|
|
||||||
lambda { subject }.should raise_error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "with a Procfile" do
|
describe "with a Procfile" do
|
||||||
before { write_procfile }
|
before { write_procfile }
|
||||||
|
|
||||||
it "reads the processes" do
|
it "reads the processes" do
|
||||||
subject.procfile["alpha"].command.should == "./alpha"
|
subject.process("alpha").command.should == "./alpha"
|
||||||
subject.procfile["bravo"].command.should == "./bravo"
|
subject.process("bravo").command.should == "./bravo"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "start" do
|
describe "start" do
|
||||||
it "forks the processes" do
|
it "forks the processes" do
|
||||||
write_procfile
|
mock(subject.process("alpha")).run(anything)
|
||||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./alpha", is_a(IO))
|
mock(subject.process("bravo")).run(anything)
|
||||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO))
|
|
||||||
mock(subject).watch_for_output
|
mock(subject).watch_for_output
|
||||||
mock(subject).watch_for_termination
|
mock(subject).watch_for_termination
|
||||||
subject.start
|
subject.start
|
||||||
end
|
end
|
||||||
|
|
||||||
it "handles concurrency" do
|
it "handles concurrency" do
|
||||||
write_procfile
|
subject.options[:formation] = "alpha=2"
|
||||||
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
|
mock(subject.process("alpha")).run(anything).twice
|
||||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./alpha", is_a(IO)).twice
|
mock(subject.process("bravo")).run(anything).never
|
||||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO)).never
|
mock(subject).watch_for_output
|
||||||
mock(engine).watch_for_output
|
mock(subject).watch_for_termination
|
||||||
mock(engine).watch_for_termination
|
subject.start
|
||||||
engine.start
|
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"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "environment" do
|
describe "environment" do
|
||||||
before(:each) do
|
it "should read env files" do
|
||||||
write_procfile
|
|
||||||
stub(Process).fork
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should read if specified" do
|
|
||||||
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
|
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
|
||||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
subject.load_env("/tmp/env")
|
||||||
stub(engine).info
|
subject.env["FOO"].should == "baz"
|
||||||
mock(engine).spawn_processes
|
|
||||||
mock(engine).watch_for_termination
|
|
||||||
engine.environment.should == {"FOO"=>"baz"}
|
|
||||||
engine.start
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should read more than one if specified" do
|
it "should read more than one if specified" do
|
||||||
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
||||||
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
||||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
|
subject.load_env "/tmp/env1"
|
||||||
stub(engine).info
|
subject.load_env "/tmp/env2"
|
||||||
mock(engine).spawn_processes
|
subject.env["FOO"].should == "bar"
|
||||||
mock(engine).watch_for_termination
|
subject.env["BAZ"].should == "qux"
|
||||||
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
|
end
|
||||||
engine.start
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
it "should fail if specified and doesnt exist" do
|
it "should fail if specified and doesnt exist" do
|
||||||
mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
|
lambda { subject.load_env "/tmp/env" }.should raise_error(Errno::ENOENT)
|
||||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should read .env if none specified" do
|
it "should set port from .env if specified" do
|
||||||
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
|
File.open("/tmp/env", "w") { |f| f.puts("PORT=9000") }
|
||||||
engine = Foreman::Engine.new("Procfile")
|
subject.load_env "/tmp/env"
|
||||||
mock(engine).spawn_processes
|
subject.send(:base_port).should == 9000
|
||||||
mock(engine).watch_for_termination
|
|
||||||
engine.environment.should == {"FOO"=>"qoo"}
|
|
||||||
engine.start
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "utf8" do
|
|
||||||
before(:each) do
|
|
||||||
File.open("Procfile", "w") do |file|
|
|
||||||
file.puts "utf8: #{resource_path("bin/utf8")}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should spawn" do
|
|
||||||
stub(subject).watch_for_output
|
|
||||||
stub(subject).watch_for_termination
|
|
||||||
subject.start
|
|
||||||
sleep 1
|
|
||||||
mock(subject).info(/started with pid \d+/, "utf8.1", anything)
|
|
||||||
mock(subject).info("\xff\x03\n", "utf8.1", anything)
|
|
||||||
subject.send(:poll_readers)
|
|
||||||
subject.send(:poll_readers)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
require "foreman/export/base"
|
require "foreman/engine"
|
||||||
|
require "foreman/export"
|
||||||
|
|
||||||
describe "Foreman::Export::Base" do
|
describe "Foreman::Export::Base", :fakefs do
|
||||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||||
let(:location) { "/tmp/init" }
|
let(:location) { "/tmp/init" }
|
||||||
let(:engine) { Foreman::Engine.new(procfile) }
|
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
|
||||||
let(:subject) { Foreman::Export::Base.new(location, engine) }
|
let(:subject) { Foreman::Export::Base.new(location, engine) }
|
||||||
|
|
||||||
it "has a say method for displaying info" do
|
it "has a say method for displaying info" do
|
||||||
@@ -12,10 +13,6 @@ describe "Foreman::Export::Base" do
|
|||||||
subject.send(:say, "foo")
|
subject.send(:say, "foo")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "export needs to be overridden" do
|
|
||||||
lambda { subject.export }.should raise_error("export method must be overridden")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises errors as a Foreman::Export::Exception" do
|
it "raises errors as a Foreman::Export::Exception" do
|
||||||
lambda { subject.send(:error, "foo") }.should raise_error(Foreman::Export::Exception, "foo")
|
lambda { subject.send(:error, "foo") }.should raise_error(Foreman::Export::Exception, "foo")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ require "foreman/export/bluepill"
|
|||||||
require "tmpdir"
|
require "tmpdir"
|
||||||
|
|
||||||
describe Foreman::Export::Bluepill, :fakefs do
|
describe Foreman::Export::Bluepill, :fakefs do
|
||||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||||
let(:engine) { Foreman::Engine.new(procfile) }
|
let(:formation) { nil }
|
||||||
let(:options) { Hash.new }
|
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||||
let(:bluepill) { Foreman::Export::Bluepill.new("/tmp/init", engine, options) }
|
let(:options) { Hash.new }
|
||||||
|
let(:bluepill) { Foreman::Export::Bluepill.new("/tmp/init", engine, options) }
|
||||||
|
|
||||||
before(:each) { load_export_templates_into_fakefs("bluepill") }
|
before(:each) { load_export_templates_into_fakefs("bluepill") }
|
||||||
before(:each) { stub(bluepill).say }
|
before(:each) { stub(bluepill).say }
|
||||||
@@ -24,8 +25,8 @@ describe Foreman::Export::Bluepill, :fakefs do
|
|||||||
bluepill.export
|
bluepill.export
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with concurrency" do
|
context "with a process formation" do
|
||||||
let(:options) { Hash[:concurrency => "alpha=2"] }
|
let(:formation) { "alpha=2" }
|
||||||
|
|
||||||
it "exports to the filesystem with concurrency" do
|
it "exports to the filesystem with concurrency" do
|
||||||
bluepill.export
|
bluepill.export
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ require "foreman/export/inittab"
|
|||||||
require "tmpdir"
|
require "tmpdir"
|
||||||
|
|
||||||
describe Foreman::Export::Inittab, :fakefs do
|
describe Foreman::Export::Inittab, :fakefs do
|
||||||
let(:location) { "/tmp/inittab" }
|
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
let(:location) { "/tmp/inittab" }
|
||||||
let(:location) { "/tmp/inittab" }
|
let(:formation) { nil }
|
||||||
let(:engine) { Foreman::Engine.new(procfile) }
|
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||||
let(:options) { Hash.new }
|
let(:options) { Hash.new }
|
||||||
let(:inittab) { Foreman::Export::Inittab.new(location, engine, options) }
|
let(:inittab) { Foreman::Export::Inittab.new(location, engine, options) }
|
||||||
|
|
||||||
before(:each) { load_export_templates_into_fakefs("inittab") }
|
before(:each) { load_export_templates_into_fakefs("inittab") }
|
||||||
before(:each) { stub(inittab).say }
|
before(:each) { stub(inittab).say }
|
||||||
@@ -29,7 +29,7 @@ describe Foreman::Export::Inittab, :fakefs do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "with concurrency" do
|
context "with concurrency" do
|
||||||
let(:options) { Hash[:concurrency => "alpha=2"] }
|
let(:formation) { "alpha=2" }
|
||||||
|
|
||||||
it "exports to the filesystem with concurrency" do
|
it "exports to the filesystem with concurrency" do
|
||||||
inittab.export
|
inittab.export
|
||||||
|
|||||||
21
spec/foreman/export/launchd_spec.rb
Normal file
21
spec/foreman/export/launchd_spec.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
require "spec_helper"
|
||||||
|
require "foreman/engine"
|
||||||
|
require "foreman/export/launchd"
|
||||||
|
require "tmpdir"
|
||||||
|
|
||||||
|
describe Foreman::Export::Launchd, :fakefs do
|
||||||
|
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||||
|
let(:options) { Hash.new }
|
||||||
|
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
|
||||||
|
let(:launchd) { Foreman::Export::Launchd.new("/tmp/init", engine, options) }
|
||||||
|
|
||||||
|
before(:each) { load_export_templates_into_fakefs("launchd") }
|
||||||
|
before(:each) { stub(launchd).say }
|
||||||
|
|
||||||
|
it "exports to the filesystem" do
|
||||||
|
launchd.export
|
||||||
|
File.read("/tmp/init/app-alpha-1.plist").should == example_export_file("launchd/launchd-a.default")
|
||||||
|
File.read("/tmp/init/app-bravo-1.plist").should == example_export_file("launchd/launchd-b.default")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -5,33 +5,28 @@ require "tmpdir"
|
|||||||
|
|
||||||
describe Foreman::Export::Runit, :fakefs do
|
describe Foreman::Export::Runit, :fakefs do
|
||||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
|
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
|
||||||
let(:engine) { Foreman::Engine.new(procfile) }
|
let(:engine) { Foreman::Engine.new(:formation => "alpha=2,bravo=1").load_procfile(procfile) }
|
||||||
let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, :concurrency => 'alpha=2,bravo=1') }
|
let(:options) { Hash.new }
|
||||||
|
let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, options) }
|
||||||
|
|
||||||
before(:each) { load_export_templates_into_fakefs("runit") }
|
before(:each) { load_export_templates_into_fakefs("runit") }
|
||||||
before(:each) { stub(runit).say }
|
before(:each) { stub(runit).say }
|
||||||
before(:each) { stub(FakeFS::FileUtils).chmod }
|
before(:each) { stub(FakeFS::FileUtils).chmod }
|
||||||
|
|
||||||
it "exports to the filesystem" do
|
it "exports to the filesystem" do
|
||||||
FileUtils.mkdir_p('/tmp/init')
|
engine.env["BAR"] = "baz"
|
||||||
|
|
||||||
runit.export
|
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/run").should == example_export_file('runit/app-alpha-1/run')
|
||||||
File.read("/tmp/init/app-alpha-1/log/run").should ==
|
File.read("/tmp/init/app-alpha-1/log/run").should == example_export_file('runit/app-alpha-1/log/run')
|
||||||
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/PORT").should == "5000\n"
|
||||||
File.read("/tmp/init/app-alpha-1/env/BAR").should == "baz\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/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/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/PORT").should == "5001\n"
|
||||||
File.read("/tmp/init/app-alpha-2/env/BAR").should == "baz\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/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/log/run").should ==
|
|
||||||
example_export_file('runit/app-bravo-1-log-run')
|
|
||||||
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
|
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
36
spec/foreman/export/supervisord_spec.rb
Normal file
36
spec/foreman/export/supervisord_spec.rb
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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
|
||||||
@@ -4,10 +4,11 @@ require "foreman/export/upstart"
|
|||||||
require "tmpdir"
|
require "tmpdir"
|
||||||
|
|
||||||
describe Foreman::Export::Upstart, :fakefs do
|
describe Foreman::Export::Upstart, :fakefs do
|
||||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
let(:procfile) { write_procfile("/tmp/app/Procfile") }
|
||||||
let(:engine) { Foreman::Engine.new(procfile) }
|
let(:formation) { nil }
|
||||||
let(:options) { Hash.new }
|
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||||
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, options) }
|
let(:options) { Hash.new }
|
||||||
|
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, options) }
|
||||||
|
|
||||||
before(:each) { load_export_templates_into_fakefs("upstart") }
|
before(:each) { load_export_templates_into_fakefs("upstart") }
|
||||||
before(:each) { stub(upstart).say }
|
before(:each) { stub(upstart).say }
|
||||||
@@ -33,8 +34,15 @@ describe Foreman::Export::Upstart, :fakefs do
|
|||||||
upstart.export
|
upstart.export
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with concurrency" do
|
it "quotes and escapes environment variables" do
|
||||||
let(:options) { Hash[:concurrency => "alpha=2"] }
|
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
|
it "exports to the filesystem with concurrency" do
|
||||||
upstart.export
|
upstart.export
|
||||||
@@ -48,38 +56,31 @@ describe Foreman::Export::Upstart, :fakefs do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "with alternate templates" do
|
context "with alternate templates" do
|
||||||
let(:template_root) { "/tmp/alternate" }
|
let(:template) { "/tmp/alternate" }
|
||||||
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, :template => template_root) }
|
let(:options) { { :app => "app", :template => template } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
FileUtils.mkdir_p template_root
|
FileUtils.mkdir_p template
|
||||||
File.open("#{template_root}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
|
File.open("#{template}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can export with alternate template files" do
|
it "can export with alternate template files" do
|
||||||
upstart.export
|
upstart.export
|
||||||
|
|
||||||
File.read("/tmp/init/app.conf").should == "alternate_template\n"
|
File.read("/tmp/init/app.conf").should == "alternate_template\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with alternate templates from home dir" do
|
context "with alternate templates from home dir" do
|
||||||
let(:default_template_root) {File.expand_path("#{ENV['HOME']}/.foreman/templates")}
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
ENV['_FOREMAN_SPEC_HOME'] = ENV['HOME']
|
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/upstart")
|
||||||
ENV['HOME'] = "/home/appuser"
|
File.open(File.expand_path("~/.foreman/templates/upstart/master.conf.erb"), "w") do |file|
|
||||||
FileUtils.mkdir_p default_template_root
|
file.puts "default_alternate_template"
|
||||||
File.open("#{default_template_root}/master.conf.erb", "w") { |f| f.puts "default_alternate_template" }
|
end
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
ENV['HOME'] = ENV.delete('_FOREMAN_SPEC_HOME')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can export with alternate template files" do
|
it "can export with alternate template files" do
|
||||||
upstart.export
|
upstart.export
|
||||||
|
|
||||||
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
|
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,127 +5,44 @@ require 'timeout'
|
|||||||
require 'tmpdir'
|
require 'tmpdir'
|
||||||
|
|
||||||
describe Foreman::Process do
|
describe Foreman::Process do
|
||||||
subject { described_class.new entry, number, port }
|
|
||||||
|
|
||||||
let(:number) { 1 }
|
def run(process, options={})
|
||||||
let(:port) { 777 }
|
rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
|
||||||
let(:command) { "script" }
|
process.run(options.merge(:output => wr))
|
||||||
let(:name) { "foobar" }
|
rd.gets
|
||||||
let(:entry) { OpenStruct.new :name => name, :command => command }
|
end
|
||||||
|
|
||||||
its(:entry) { entry }
|
describe "#run" do
|
||||||
its(:num) { number }
|
|
||||||
its(:port) { port }
|
|
||||||
its(:name) { "#{name}.#{port}" }
|
|
||||||
its(:pid) { nil }
|
|
||||||
|
|
||||||
describe '#run' do
|
it "runs the process" do
|
||||||
let(:pipe) { :pipe }
|
process = Foreman::Process.new(resource_path("bin/test"))
|
||||||
let(:basedir) { Dir.mktmpdir }
|
run(process).should == "testing\n"
|
||||||
let(:env) {{ 'foo' => 'bar' }}
|
|
||||||
let(:init_delta) { 0.1 }
|
|
||||||
|
|
||||||
after { FileUtils.remove_entry_secure basedir }
|
|
||||||
|
|
||||||
def run(cmd=command)
|
|
||||||
entry.command = cmd
|
|
||||||
subject.run pipe, basedir, env
|
|
||||||
subject.detach && sleep(init_delta)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_file(executable, code)
|
it "can set environment" do
|
||||||
file = File.open("#{basedir}/script", 'w') {|it| it << code }
|
process = Foreman::Process.new(resource_path("bin/env FOO"), :env => { "FOO" => "bar" })
|
||||||
run "#{executable} #{file.path}"
|
run(process).should == "bar\n"
|
||||||
sleep 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'options' do
|
it "can set per-run environment" do
|
||||||
it 'should set PORT for environment' do
|
process = Foreman::Process.new(resource_path("bin/env FOO"))
|
||||||
mock(subject).run_process(basedir, command, pipe) do
|
run(process, :env => { "FOO" => "bar "}).should == "bar\n"
|
||||||
ENV['PORT'].should == port.to_s
|
|
||||||
end
|
|
||||||
run
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should set custom variables for environment' do
|
|
||||||
mock(subject).run_process(basedir, command, pipe) do
|
|
||||||
ENV['foo'].should == 'bar'
|
|
||||||
end
|
|
||||||
run
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should restore environment afterwards' do
|
|
||||||
mock(subject).run_process(basedir, command, pipe)
|
|
||||||
run
|
|
||||||
ENV.should_not include('PORT', 'foo')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'process' do
|
it "can handle env vars in the command" do
|
||||||
around do |spec|
|
process = Foreman::Process.new(resource_path("bin/echo $FOO"), :env => { "FOO" => "bar" })
|
||||||
IO.pipe do |reader, writer|
|
run(process).should == "bar\n"
|
||||||
@reader, @writer = reader, writer
|
end
|
||||||
spec.run
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:pipe) { @writer }
|
it "can handle per-run env vars in the command" do
|
||||||
let(:output) { @reader.read_nonblock 1024 }
|
process = Foreman::Process.new(resource_path("bin/echo $FOO"))
|
||||||
|
run(process, :env => { "FOO" => "bar" }).should == "bar\n"
|
||||||
|
end
|
||||||
|
|
||||||
it 'should not block' do
|
it "should output utf8 properly" do
|
||||||
expect {
|
process = Foreman::Process.new(resource_path("bin/utf8"))
|
||||||
Timeout.timeout(2*init_delta) { run 'sleep 2' }
|
run(process).should == "\xFF\x03\n"
|
||||||
}.should_not raise_exception
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should be alive' do
|
|
||||||
run 'sleep 1'
|
|
||||||
subject.should be_alive
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should be dead' do
|
|
||||||
run 'exit'
|
|
||||||
subject.should be_dead
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should be killable' do
|
|
||||||
run 'sleep 1'
|
|
||||||
subject.kill 'TERM'
|
|
||||||
subject.should be_dead
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should send different signals' do
|
|
||||||
run_file 'ruby', <<-CODE
|
|
||||||
trap "TERM", "IGNORE"
|
|
||||||
loop { sleep 1 }
|
|
||||||
CODE
|
|
||||||
subject.should be_alive
|
|
||||||
subject.kill 'TERM'
|
|
||||||
subject.should be_alive
|
|
||||||
subject.kill 'KILL'
|
|
||||||
subject.should be_dead
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should redirect stdout' do
|
|
||||||
run 'echo hey'
|
|
||||||
output.should include('hey')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should redirect stderr' do
|
|
||||||
run 'echo hey >2'
|
|
||||||
output.should include('hey')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should handle variables' do
|
|
||||||
run 'echo $PORT'
|
|
||||||
output.should include('777')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should handle arguments' do
|
|
||||||
pending
|
|
||||||
run %{ sh -c "trap '' TERM; sleep 10" }
|
|
||||||
subject.should be_alive
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
41
spec/foreman/procfile_spec.rb
Normal file
41
spec/foreman/procfile_spec.rb
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'foreman/procfile'
|
||||||
|
require 'pathname'
|
||||||
|
require 'tmpdir'
|
||||||
|
|
||||||
|
describe Foreman::Procfile, :fakefs do
|
||||||
|
subject { Foreman::Procfile.new }
|
||||||
|
|
||||||
|
it "can load from a file" do
|
||||||
|
write_procfile
|
||||||
|
subject.load "Procfile"
|
||||||
|
subject["alpha"].should == "./alpha"
|
||||||
|
subject["bravo"].should == "./bravo"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "loads a passed-in Procfile" do
|
||||||
|
write_procfile
|
||||||
|
procfile = Foreman::Procfile.new("Procfile")
|
||||||
|
procfile["alpha"].should == "./alpha"
|
||||||
|
procfile["bravo"].should == "./bravo"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can have a process appended to it" do
|
||||||
|
subject["charlie"] = "./charlie"
|
||||||
|
subject["charlie"].should == "./charlie"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can write to a string" do
|
||||||
|
subject["foo"] = "./foo"
|
||||||
|
subject["bar"] = "./bar"
|
||||||
|
subject.to_s.should == "foo: ./foo\nbar: ./bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can write to a file" do
|
||||||
|
subject["foo"] = "./foo"
|
||||||
|
subject["bar"] = "./bar"
|
||||||
|
subject.save "/tmp/proc"
|
||||||
|
File.read("/tmp/proc").should == "foo: ./foo\nbar: ./bar\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -8,24 +8,6 @@ describe Foreman do
|
|||||||
it { should be_a String }
|
it { should be_a String }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "::load_env!(env_file)", :fakefs do
|
|
||||||
after do
|
|
||||||
ENV['FOO'] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should load env_file into ENV" do
|
|
||||||
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
|
||||||
Foreman.load_env!("/tmp/env1")
|
|
||||||
ENV['FOO'].should == 'bar'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should assume env_file in ./.env" do
|
|
||||||
File.open("./.env", "w") { |f| f.puts("FOO=bar") }
|
|
||||||
Foreman.load_env!
|
|
||||||
ENV['FOO'].should == 'bar'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "runner" do
|
describe "runner" do
|
||||||
it "should exist" do
|
it "should exist" do
|
||||||
File.exists?(Foreman.runner).should == true
|
File.exists?(Foreman.runner).should == true
|
||||||
|
|||||||
1
spec/resources/.env
Normal file
1
spec/resources/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FOO=bar
|
||||||
4
spec/resources/Procfile
Normal file
4
spec/resources/Procfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
echo: bin/echo echoing
|
||||||
|
env: bin/env FOO
|
||||||
|
test: bin/test
|
||||||
|
utf8: bin/utf8
|
||||||
2
spec/resources/bin/echo
Executable file
2
spec/resources/bin/echo
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo $*
|
||||||
2
spec/resources/bin/env
Executable file
2
spec/resources/bin/env
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo ${!1}
|
||||||
2
spec/resources/bin/test
Executable file
2
spec/resources/bin/test
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "testing"
|
||||||
@@ -11,13 +11,14 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
|
|
||||||
process.working_dir = "/tmp/app"
|
process.working_dir = "/tmp/app"
|
||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5000"}
|
process.environment = {"PORT"=>"5000"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
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.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
||||||
|
|
||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "app-alpha"
|
process.group = "app-alpha"
|
||||||
@@ -29,13 +30,14 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
|
|
||||||
process.working_dir = "/tmp/app"
|
process.working_dir = "/tmp/app"
|
||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5001"}
|
process.environment = {"PORT"=>"5001"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
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.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
|
||||||
|
|
||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "app-alpha"
|
process.group = "app-alpha"
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
|
|
||||||
process.working_dir = "/tmp/app"
|
process.working_dir = "/tmp/app"
|
||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5000"}
|
process.environment = {"PORT"=>"5000"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
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.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
||||||
|
|
||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "app-alpha"
|
process.group = "app-alpha"
|
||||||
@@ -28,13 +29,14 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
|
|
||||||
process.working_dir = "/tmp/app"
|
process.working_dir = "/tmp/app"
|
||||||
process.daemonize = true
|
process.daemonize = true
|
||||||
process.environment = {"PORT" => "5100"}
|
process.environment = {"PORT"=>"5100"}
|
||||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||||
|
process.stop_grace_time = 45.seconds
|
||||||
|
|
||||||
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
|
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
|
||||||
|
|
||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "app-bravo"
|
process.group = "app-bravo"
|
||||||
|
|||||||
22
spec/resources/export/launchd/launchd-a.default
Normal file
22
spec/resources/export/launchd/launchd-a.default
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>app-alpha-1</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>./alpha</string>
|
||||||
|
</array>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/var/log/app/app-alpha-1.log</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>/tmp/app</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
22
spec/resources/export/launchd/launchd-b.default
Normal file
22
spec/resources/export/launchd/launchd-b.default
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>app-bravo-1</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>./bravo</string>
|
||||||
|
</array>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/var/log/app/app-bravo-1.log</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>app</string>
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>/tmp/app</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
24
spec/resources/export/supervisord/app-alpha-1.conf
Normal file
24
spec/resources/export/supervisord/app-alpha-1.conf
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
[program:app-alpha-1]
|
||||||
|
command=./alpha
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopsignal=QUIT
|
||||||
|
stdout_logfile=/var/log/app/alpha-1.log
|
||||||
|
stderr_logfile=/var/log/app/alpha-1.error.log
|
||||||
|
user=app
|
||||||
|
directory=/tmp/app
|
||||||
|
environment=PORT="5000"
|
||||||
|
[program:app-bravo-1]
|
||||||
|
command=./bravo
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopsignal=QUIT
|
||||||
|
stdout_logfile=/var/log/app/bravo-1.log
|
||||||
|
stderr_logfile=/var/log/app/bravo-1.error.log
|
||||||
|
user=app
|
||||||
|
directory=/tmp/app
|
||||||
|
environment=PORT="5100"
|
||||||
|
|
||||||
|
[group:app]
|
||||||
|
programs=app-alpha-1,app-bravo-1
|
||||||
24
spec/resources/export/supervisord/app-alpha-2.conf
Normal file
24
spec/resources/export/supervisord/app-alpha-2.conf
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
[program:app-alpha-1]
|
||||||
|
command=./alpha
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopsignal=QUIT
|
||||||
|
stdout_logfile=/var/log/app/alpha-1.log
|
||||||
|
stderr_logfile=/var/log/app/alpha-1.error.log
|
||||||
|
user=app
|
||||||
|
directory=/tmp/app
|
||||||
|
environment=PORT="5000"
|
||||||
|
[program:app-alpha-2]
|
||||||
|
command=./alpha
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopsignal=QUIT
|
||||||
|
stdout_logfile=/var/log/app/alpha-2.log
|
||||||
|
stderr_logfile=/var/log/app/alpha-2.error.log
|
||||||
|
user=app
|
||||||
|
directory=/tmp/app
|
||||||
|
environment=PORT="5001"
|
||||||
|
|
||||||
|
[group:app]
|
||||||
|
programs=app-alpha-1,app-alpha-2
|
||||||
@@ -6,6 +6,7 @@ SimpleCov.start do
|
|||||||
end
|
end
|
||||||
|
|
||||||
require "rspec"
|
require "rspec"
|
||||||
|
require "timecop"
|
||||||
require "fakefs/safe"
|
require "fakefs/safe"
|
||||||
require "fakefs/spec_helpers"
|
require "fakefs/spec_helpers"
|
||||||
|
|
||||||
@@ -23,7 +24,39 @@ def mock_error(subject, message)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def foreman(args)
|
def foreman(args)
|
||||||
Foreman::CLI.start(args.split(" "))
|
capture_stdout do
|
||||||
|
begin
|
||||||
|
Foreman::CLI.start(args.split(" "))
|
||||||
|
rescue SystemExit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def forked_foreman(args)
|
||||||
|
rd, wr = IO.pipe("BINARY")
|
||||||
|
Process.spawn("bundle exec bin/foreman #{args}", :out => wr, :err => wr)
|
||||||
|
wr.close
|
||||||
|
rd.read
|
||||||
|
end
|
||||||
|
|
||||||
|
def fork_and_capture(&blk)
|
||||||
|
rd, wr = IO.pipe("BINARY")
|
||||||
|
pid = fork do
|
||||||
|
rd.close
|
||||||
|
wr.sync = true
|
||||||
|
$stdout.reopen wr
|
||||||
|
$stderr.reopen wr
|
||||||
|
blk.call
|
||||||
|
$stdout.flush
|
||||||
|
$stdout.close
|
||||||
|
end
|
||||||
|
wr.close
|
||||||
|
Process.wait pid
|
||||||
|
buffer = ""
|
||||||
|
until rd.eof?
|
||||||
|
p [:foo]
|
||||||
|
buffer += rd.gets
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mock_exit(&block)
|
def mock_exit(&block)
|
||||||
@@ -55,13 +88,21 @@ def write_env(env=".env", options={"FOO"=>"bar"})
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_export_templates_into_fakefs(type)
|
def without_fakefs
|
||||||
FakeFS.deactivate!
|
FakeFS.deactivate!
|
||||||
files = Dir[File.expand_path("../../data/export/#{type}/**", __FILE__)].inject({}) do |hash, file|
|
ret = yield
|
||||||
hash.update(file => File.read(file))
|
|
||||||
end
|
|
||||||
FakeFS.activate!
|
FakeFS.activate!
|
||||||
files.each do |filename, contents|
|
ret
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_export_templates_into_fakefs(type)
|
||||||
|
without_fakefs do
|
||||||
|
Dir[File.expand_path("../../data/export/#{type}/**/*", __FILE__)].inject({}) do |hash, file|
|
||||||
|
next(hash) if File.directory?(file)
|
||||||
|
hash.update(file => File.read(file))
|
||||||
|
end
|
||||||
|
end.each do |filename, contents|
|
||||||
|
FileUtils.mkdir_p File.dirname(filename)
|
||||||
File.open(filename, "w") do |f|
|
File.open(filename, "w") do |f|
|
||||||
f.puts contents
|
f.puts contents
|
||||||
end
|
end
|
||||||
@@ -93,6 +134,17 @@ def normalize_space(s)
|
|||||||
s.gsub(/\n[\n\s]*/, "\n")
|
s.gsub(/\n[\n\s]*/, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def capture_stdout
|
||||||
|
old_stdout = $stdout.dup
|
||||||
|
rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
|
||||||
|
$stdout = wr
|
||||||
|
yield
|
||||||
|
wr.close
|
||||||
|
rd.read
|
||||||
|
ensure
|
||||||
|
$stdout = old_stdout
|
||||||
|
end
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.treat_symbols_as_metadata_keys_with_true_values = true
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
||||||
config.color_enabled = true
|
config.color_enabled = true
|
||||||
|
|||||||
@@ -27,14 +27,6 @@ task :pages => "man:commit" do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Generate an authors list"
|
|
||||||
task :authors do
|
|
||||||
authors = %x{ git log --pretty=format:"%an" | sort -u }.split("\n")
|
|
||||||
readme = File.read("README.md")
|
|
||||||
readme.gsub!(/#### Patches contributed by\n([^\n]*)\n/m, "#### Patches contributed by\n#{authors.join(", ")}\n")
|
|
||||||
File.open("README.md", "w") { |f| f.print readme }
|
|
||||||
end
|
|
||||||
|
|
||||||
def latest_release
|
def latest_release
|
||||||
latest = File.read("Changelog.md").split("\n").first.split(" ")[1]
|
latest = File.read("Changelog.md").split("\n").first.split(" ")[1]
|
||||||
end
|
end
|
||||||
@@ -43,7 +35,7 @@ def newer_release
|
|||||||
tags = %x{ git tag --contains v#{latest_release} }.split("\n").sort_by do |tag|
|
tags = %x{ git tag --contains v#{latest_release} }.split("\n").sort_by do |tag|
|
||||||
Gem::Version.new(tag[1..-1])
|
Gem::Version.new(tag[1..-1])
|
||||||
end
|
end
|
||||||
tags.reject { |tag| Gem::Version.new(tag[1..-1]).prerelease? }[1]
|
tags[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Generate a Changelog"
|
desc "Generate a Changelog"
|
||||||
|
|||||||
Reference in New Issue
Block a user