Compare commits
369 Commits
v0.1.0
...
v0.37.0.pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2995a605b4 | ||
|
|
ef280e802d | ||
|
|
8a9001842c | ||
|
|
8a8d31eb43 | ||
|
|
3e98170878 | ||
|
|
3d84de3062 | ||
|
|
9cc0afca49 | ||
|
|
7a25d3ac5a | ||
|
|
db1a5df354 | ||
|
|
e137596ce0 | ||
|
|
c9042c5aae | ||
|
|
550adc8070 | ||
|
|
fbdde3e62a | ||
|
|
e161ecb630 | ||
|
|
853a88dfbf | ||
|
|
b4cab08327 | ||
|
|
a008886bd0 | ||
|
|
c62f892ff6 | ||
|
|
d885e019b3 | ||
|
|
cfd337b44d | ||
|
|
1485eeb859 | ||
|
|
e0b5928e88 | ||
|
|
a73dce5405 | ||
|
|
2abddb42b3 | ||
|
|
d961a32cfe | ||
|
|
2bfc065c1d | ||
|
|
fbe3d4ec69 | ||
|
|
631187e0d8 | ||
|
|
92d1a4d367 | ||
|
|
f4123f4ae1 | ||
|
|
d4c2332c59 | ||
|
|
e257fc89c1 | ||
|
|
a278755ae4 | ||
|
|
3367a060a7 | ||
|
|
ac7e0743ac | ||
|
|
e574880814 | ||
|
|
7132cacbf6 | ||
|
|
c1f279aa6f | ||
|
|
34cfe9ef9d | ||
|
|
79fc3b8029 | ||
|
|
91140638e1 | ||
|
|
48cc60c30f | ||
|
|
533139ea9f | ||
|
|
86e2056a24 | ||
|
|
ab29963ee4 | ||
|
|
cf269c39da | ||
|
|
76cd2e794b | ||
|
|
83748cb538 | ||
|
|
d2c9ce0f34 | ||
|
|
98337c92e1 | ||
|
|
33d738b3f8 | ||
|
|
9432989fbe | ||
|
|
66b1483a75 | ||
|
|
64bd4db128 | ||
|
|
b561555f3a | ||
|
|
baa7b7685c | ||
|
|
cfa6e6f259 | ||
|
|
a34bc59721 | ||
|
|
07e8ca4a4b | ||
|
|
342d30bbb8 | ||
|
|
268dd6240e | ||
|
|
9e60b3e1a4 | ||
|
|
1c6285f8af | ||
|
|
5de1bd18ac | ||
|
|
fff15bc627 | ||
|
|
a66157d611 | ||
|
|
fcfa913fb0 | ||
|
|
fc438472f9 | ||
|
|
fc95936327 | ||
|
|
0c27f78d46 | ||
|
|
356c61f471 | ||
|
|
dcff4da220 | ||
|
|
888520ee99 | ||
|
|
c7b6b334fd | ||
|
|
f476920a05 | ||
|
|
5436b68cf1 | ||
|
|
c9411cd2b1 | ||
|
|
6e95d1ce94 | ||
|
|
c5548a345e | ||
|
|
f668b87660 | ||
|
|
914a1ee958 | ||
|
|
e1c2946718 | ||
|
|
6160246da0 | ||
|
|
2e8030dbd4 | ||
|
|
58e4cdafd7 | ||
|
|
44a5dff724 | ||
|
|
e33921f083 | ||
|
|
79cf541ebe | ||
|
|
8bc8cb4b2e | ||
|
|
39ace26ae1 | ||
|
|
c383359136 | ||
|
|
a5e094353c | ||
|
|
12720b4c12 | ||
|
|
1c732a4658 | ||
|
|
8908a66e90 | ||
|
|
f63c0b0ca0 | ||
|
|
676d3ff8d1 | ||
|
|
615aada17e | ||
|
|
2e1d1c7c15 | ||
|
|
bf832ffd9f | ||
|
|
b9bfede48a | ||
|
|
bed8323029 | ||
|
|
f6ef5a5b4f | ||
|
|
f3c1e76860 | ||
|
|
33aa1efc90 | ||
|
|
caa688cdc6 | ||
|
|
c6a410b664 | ||
|
|
02c8d2cb10 | ||
|
|
ada41ce311 | ||
|
|
8f1c752a77 | ||
|
|
ddf25fe0ea | ||
|
|
9dac91a624 | ||
|
|
cdaeb646ac | ||
|
|
86e4251840 | ||
|
|
ba18f7e589 | ||
|
|
be73e8500f | ||
|
|
d26ca669a1 | ||
|
|
a3e758ab6c | ||
|
|
5dc232a7b1 | ||
|
|
4191cb7b9c | ||
|
|
90d4dffb82 | ||
|
|
823f307abc | ||
|
|
ed44a11e21 | ||
|
|
5719f4fc72 | ||
|
|
47008fb921 | ||
|
|
91edac3197 | ||
|
|
20e598abcc | ||
|
|
ec7f4a480d | ||
|
|
51376058d4 | ||
|
|
f7542083dd | ||
|
|
23124afc7e | ||
|
|
c948858f45 | ||
|
|
f358d897fa | ||
|
|
ec1b06abb5 | ||
|
|
75d4fc562d | ||
|
|
1cbb295b0d | ||
|
|
60fb125b51 | ||
|
|
69d596bb8b | ||
|
|
cbd5a6344c | ||
|
|
4230455859 | ||
|
|
b25dfee62d | ||
|
|
b73da71c9c | ||
|
|
43558758fc | ||
|
|
6da8aca609 | ||
|
|
9df93a64cc | ||
|
|
8fd9b753f4 | ||
|
|
7fc6d02e7b | ||
|
|
ddcaac8749 | ||
|
|
9db97abb10 | ||
|
|
bda6e30323 | ||
|
|
ffd77312bb | ||
|
|
a24c4ce17b | ||
|
|
cf3d7a0b12 | ||
|
|
21a041527c | ||
|
|
6ad344d214 | ||
|
|
3b8fec463d | ||
|
|
4e015b7436 | ||
|
|
2042de7732 | ||
|
|
64338c5a09 | ||
|
|
8cef71766c | ||
|
|
a2ba079665 | ||
|
|
c8d0dba1cb | ||
|
|
e8d2552caa | ||
|
|
927a57f738 | ||
|
|
f65538c1b1 | ||
|
|
470c8043af | ||
|
|
3e69b27f5f | ||
|
|
f4b1e3701b | ||
|
|
fd234b8044 | ||
|
|
f2f09554c8 | ||
|
|
6c465b4ef1 | ||
|
|
c419f8213b | ||
|
|
7f61fb61ea | ||
|
|
577a5c7c5c | ||
|
|
56940c56d9 | ||
|
|
f308ad886d | ||
|
|
55375b9bde | ||
|
|
85fcccffa8 | ||
|
|
58fc18d015 | ||
|
|
f7c9802ef7 | ||
|
|
1c00d65f29 | ||
|
|
9193a675a3 | ||
|
|
8f0b14810c | ||
|
|
124e27ed22 | ||
|
|
f34f161899 | ||
|
|
191581fe85 | ||
|
|
b98d558bed | ||
|
|
180f63624e | ||
|
|
13fd1188ad | ||
|
|
92c2b15785 | ||
|
|
4a2d7565b7 | ||
|
|
49720e0458 | ||
|
|
c4de19f4da | ||
|
|
490c8b73bf | ||
|
|
f622f43cf4 | ||
|
|
9ab701f5b9 | ||
|
|
1a5d2428b9 | ||
|
|
564aa740e1 | ||
|
|
95115f4e78 | ||
|
|
e5828f8442 | ||
|
|
f121d04bf6 | ||
|
|
3dc113fcd4 | ||
|
|
22ad6c5dd8 | ||
|
|
b117ef5b08 | ||
|
|
35474ad1a5 | ||
|
|
fc5e86b269 | ||
|
|
7f8ca7da39 | ||
|
|
31239d98d0 | ||
|
|
472395edf8 | ||
|
|
6b7d5e5161 | ||
|
|
977c80ffdd | ||
|
|
2804316bbb | ||
|
|
26859c2ec2 | ||
|
|
27152b0e76 | ||
|
|
160945b499 | ||
|
|
f2be566051 | ||
|
|
a504a59f0b | ||
|
|
e6b61801b1 | ||
|
|
dc231f072b | ||
|
|
b77b23b306 | ||
|
|
185617dddc | ||
|
|
9e4cc02827 | ||
|
|
8b6e2481f4 | ||
|
|
79f376368c | ||
|
|
3576ae82af | ||
|
|
303d54155f | ||
|
|
2e36cbf045 | ||
|
|
d3059ca563 | ||
|
|
9e42dfb253 | ||
|
|
eca48170a5 | ||
|
|
86e3cd12dd | ||
|
|
efd5a786f5 | ||
|
|
7e9117812f | ||
|
|
55f405a2b4 | ||
|
|
615bb0d4ba | ||
|
|
0ae144e468 | ||
|
|
0c2b2a4ac2 | ||
|
|
e68946f186 | ||
|
|
52edb7fd28 | ||
|
|
8446528f5a | ||
|
|
d41a8726bd | ||
|
|
7bb1e58879 | ||
|
|
62b6cac741 | ||
|
|
2a7dadc2b2 | ||
|
|
9cd772ac0f | ||
|
|
2b27d0a51a | ||
|
|
99204d7c1d | ||
|
|
e9b5ed81b8 | ||
|
|
7d751470d2 | ||
|
|
68c1a01f15 | ||
|
|
08f9027cb4 | ||
|
|
4f7692bed9 | ||
|
|
dd95cea997 | ||
|
|
f3988b0c52 | ||
|
|
fbb17dd37d | ||
|
|
31a72b454b | ||
|
|
e5a8c38da6 | ||
|
|
d199ef2b4d | ||
|
|
7a1895e435 | ||
|
|
151ddb45c8 | ||
|
|
51513dcb6d | ||
|
|
0b6fdad3a2 | ||
|
|
096f532624 | ||
|
|
efeef5b4f0 | ||
|
|
0eb08dd8ae | ||
|
|
92ba6e0ba7 | ||
|
|
2b90c48eb4 | ||
|
|
dbfd8ba49a | ||
|
|
d6837177cd | ||
|
|
58b45c4933 | ||
|
|
fbdf4d7220 | ||
|
|
895672efe8 | ||
|
|
8597e0dc16 | ||
|
|
408ba06c3f | ||
|
|
a0f82840eb | ||
|
|
1317013898 | ||
|
|
6a7720872f | ||
|
|
b3a5fa9c1b | ||
|
|
41e095cf04 | ||
|
|
2c9f6c25fc | ||
|
|
ce0261c3de | ||
|
|
f138d26e7e | ||
|
|
6000e837fe | ||
|
|
02299c4c1c | ||
|
|
6dc9fe2667 | ||
|
|
a61d808487 | ||
|
|
5f98544dab | ||
|
|
99da671f5d | ||
|
|
26599f630f | ||
|
|
cfe6a49900 | ||
|
|
ddccab4c63 | ||
|
|
3151663f37 | ||
|
|
98486513b6 | ||
|
|
b969e03086 | ||
|
|
be593846e2 | ||
|
|
2458c4e75f | ||
|
|
314e2f5530 | ||
|
|
d6c5e6ddea | ||
|
|
261164e694 | ||
|
|
92e637a231 | ||
|
|
08b94716f2 | ||
|
|
44d589a28f | ||
|
|
4bf1f26032 | ||
|
|
2a2786e676 | ||
|
|
91811425aa | ||
|
|
adb40881d7 | ||
|
|
fd3dc590d9 | ||
|
|
8651bbdbee | ||
|
|
ced0d0aa9d | ||
|
|
10e572de94 | ||
|
|
426241d461 | ||
|
|
5a258b8dc3 | ||
|
|
eeeef65c88 | ||
|
|
d67a2f4e11 | ||
|
|
ce5c8b4c04 | ||
|
|
9d859dae92 | ||
|
|
04884366b3 | ||
|
|
8c78d1e1ee | ||
|
|
35a5f972fe | ||
|
|
868bc44a4e | ||
|
|
644956db29 | ||
|
|
bd07ed809d | ||
|
|
26bb8995c9 | ||
|
|
7005860c3c | ||
|
|
90356ca41d | ||
|
|
2ce3a15bb7 | ||
|
|
f960277ae8 | ||
|
|
4f5402af4a | ||
|
|
a27f964881 | ||
|
|
cf008385b4 | ||
|
|
f633a579d6 | ||
|
|
ea90bf3615 | ||
|
|
c65c71b1c0 | ||
|
|
6f10f4f014 | ||
|
|
be6d1b805c | ||
|
|
58e936a7e2 | ||
|
|
d4f29d6909 | ||
|
|
571163795f | ||
|
|
845ee9ef38 | ||
|
|
d5d774c9c2 | ||
|
|
b77952ff7f | ||
|
|
3495fa2fea | ||
|
|
4741dceeb0 | ||
|
|
dc1cb08d27 | ||
|
|
b51e8046ce | ||
|
|
922eb7438e | ||
|
|
6811a8d96a | ||
|
|
0f7eec061d | ||
|
|
27e53eb916 | ||
|
|
b123e6b3c5 | ||
|
|
c2000484bb | ||
|
|
75f0ce4b9c | ||
|
|
d508d44fd2 | ||
|
|
35cc880d40 | ||
|
|
6434db289b | ||
|
|
1a118ee92b | ||
|
|
7bd176063c | ||
|
|
fff958f7b6 | ||
|
|
7888ee8c52 | ||
|
|
a6197c183e | ||
|
|
41a0620126 | ||
|
|
86654d7918 | ||
|
|
9929165d17 | ||
|
|
2b8d575aab | ||
|
|
56f8603a5d | ||
|
|
3f48d7c541 | ||
|
|
675ad2630d | ||
|
|
9004bf67e1 | ||
|
|
19c425d200 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,3 +1,8 @@
|
||||
coverage
|
||||
example/log/*
|
||||
pkg
|
||||
/.bundle
|
||||
/coverage
|
||||
/example/log/*
|
||||
/man/*.html
|
||||
/man/*.markdown
|
||||
/pkg
|
||||
/tags
|
||||
/vendor
|
||||
14
.travis.yml
Normal file
14
.travis.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
script: bundle exec rake spec
|
||||
|
||||
rvm:
|
||||
- 1.8.7
|
||||
- 1.9.2
|
||||
- 1.9.3
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
on_success: always
|
||||
on_failure: always
|
||||
urls:
|
||||
- https://dx-helper.herokuapp.com/travis
|
||||
11
Changelog
Normal file
11
Changelog
Normal file
@@ -0,0 +1,11 @@
|
||||
0.26.1 12/05/2011 6160246da0fafe9cf8fde188d94bbc6babc667dc
|
||||
==========================================================
|
||||
|
||||
Merge pull request #103 from csquared/load_env_from_irb [David Dollar]
|
||||
refactor load_env to apply_environment [Chris Continanza]
|
||||
rename load! to load_env! [Chris Continanza]
|
||||
use ./.env as default [Chris Continanza]
|
||||
load contents from env file [Chris Continanza]
|
||||
refactor engine to expose env methods [Chris Continanza]
|
||||
disable email notifications [David Dollar]
|
||||
add travis config [David Dollar]
|
||||
19
Gemfile
Normal file
19
Gemfile
Normal file
@@ -0,0 +1,19 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gemspec
|
||||
|
||||
platform :jruby do
|
||||
gem "posix-spawn", "~> 0.3.6"
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'parka'
|
||||
gem 'rake'
|
||||
gem 'ronn'
|
||||
gem 'fakefs', '~> 0.3.2'
|
||||
gem 'rcov', '~> 0.9.8'
|
||||
gem 'rr', '~> 1.0.2'
|
||||
gem 'rspec', '~> 2.0'
|
||||
gem 'aws-s3'
|
||||
gem "rubyzip"
|
||||
end
|
||||
67
Gemfile.lock
Normal file
67
Gemfile.lock
Normal file
@@ -0,0 +1,67 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
foreman (0.37.0.pre2)
|
||||
term-ansicolor (~> 1.0.7)
|
||||
thor (>= 0.13.6)
|
||||
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
aws-s3 (0.6.2)
|
||||
builder
|
||||
mime-types
|
||||
xml-simple
|
||||
builder (3.0.0)
|
||||
crack (0.1.8)
|
||||
diff-lcs (1.1.3)
|
||||
fakefs (0.3.2)
|
||||
hpricot (0.8.2)
|
||||
hpricot (0.8.2-java)
|
||||
mime-types (1.16)
|
||||
mustache (0.11.2)
|
||||
parka (0.6.2)
|
||||
crack
|
||||
rest-client
|
||||
thor
|
||||
posix-spawn (0.3.6)
|
||||
rake (0.9.2.2)
|
||||
rcov (0.9.8)
|
||||
rcov (0.9.8-java)
|
||||
rdiscount (1.6.5)
|
||||
rest-client (1.6.1)
|
||||
mime-types (>= 1.16)
|
||||
ronn (0.7.3)
|
||||
hpricot (>= 0.8.2)
|
||||
mustache (>= 0.7.0)
|
||||
rdiscount (>= 1.5.8)
|
||||
rr (1.0.2)
|
||||
rspec (2.8.0)
|
||||
rspec-core (~> 2.8.0)
|
||||
rspec-expectations (~> 2.8.0)
|
||||
rspec-mocks (~> 2.8.0)
|
||||
rspec-core (2.8.0)
|
||||
rspec-expectations (2.8.0)
|
||||
diff-lcs (~> 1.1.2)
|
||||
rspec-mocks (2.8.0)
|
||||
rubyzip (0.9.4)
|
||||
term-ansicolor (1.0.7)
|
||||
thor (0.14.6)
|
||||
xml-simple (1.0.15)
|
||||
|
||||
PLATFORMS
|
||||
java
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
aws-s3
|
||||
fakefs (~> 0.3.2)
|
||||
foreman!
|
||||
parka
|
||||
posix-spawn (~> 0.3.6)
|
||||
rake
|
||||
rcov (~> 0.9.8)
|
||||
ronn
|
||||
rr (~> 1.0.2)
|
||||
rspec (~> 2.0)
|
||||
rubyzip
|
||||
50
README.markdown
Normal file
50
README.markdown
Normal file
@@ -0,0 +1,50 @@
|
||||
# Foreman
|
||||
|
||||
## Installation
|
||||
|
||||
* Rubygems
|
||||
|
||||
gem install foreman
|
||||
|
||||
* OSX
|
||||
|
||||
http://assets.foreman.io/foreman/foreman.pkg
|
||||
|
||||
* Standalone Tarball
|
||||
|
||||
http://assets.foreman.io/foreman/foreman.tgz
|
||||
|
||||
## Description
|
||||
|
||||
http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
|
||||
|
||||
## Manual
|
||||
|
||||
* [man page](http://ddollar.github.com/foreman)
|
||||
* [wiki](http://github.com/ddollar/foreman/wiki)
|
||||
|
||||
## Authorship
|
||||
|
||||
Created by David Dollar
|
||||
|
||||
Patches contributed by:
|
||||
|
||||
* Adam Wiggins
|
||||
* Dan Peterson
|
||||
* Hunter Nield
|
||||
* Jay Zeschin
|
||||
* Keith Rarick
|
||||
* Khaja Minhajuddin
|
||||
* Matt Haynes
|
||||
* Michael van Rooijen
|
||||
* Mike Javorski
|
||||
* Nathan L Smith
|
||||
* Nick Zadrozny
|
||||
* Ricardo Chimal, Jr
|
||||
* Thom May
|
||||
* clifff
|
||||
* Greg Reinacker
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
77
README.rdoc
77
README.rdoc
@@ -1,77 +0,0 @@
|
||||
= Foreman
|
||||
|
||||
=== Procfile
|
||||
|
||||
alpha ./bin/alpha
|
||||
bravo ./bin/bravo some args
|
||||
charlie ./bin/charlie -n 5
|
||||
|
||||
== Development mode
|
||||
|
||||
=== Running
|
||||
|
||||
$ foreman start
|
||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [alpha] started with pid 4393
|
||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [bravo] started with pid 4394
|
||||
[foreman] [Tue May 18 01:27:08 UTC 2010] [charlie] started with pid 4395
|
||||
|
||||
=== Standardized Logging
|
||||
|
||||
log/alpha.log
|
||||
log/bravo.log
|
||||
log/charlie.log
|
||||
|
||||
== Upstart
|
||||
|
||||
=== Export to upstart scripts
|
||||
|
||||
$ foreman export sampleapp
|
||||
|
||||
$ initctl list | grep sampleapp
|
||||
sampleapp start/running
|
||||
sampleapp-alpha (1) start/running, process 4204
|
||||
sampleapp-bravo (1) start/running, process 4589
|
||||
sampleapp-charlie (1) start/running, process 4597
|
||||
|
||||
=== Change process concurrency levels
|
||||
|
||||
$ foreman scale sampleapp alpha 4
|
||||
sampleapp-alpha (2) start/running, process 4164
|
||||
sampleapp-alpha (3) start/running, process 4166
|
||||
sampleapp-alpha (4) start/running, process 4168
|
||||
|
||||
$ initctl list | grep sampleapp
|
||||
sampleapp start/running
|
||||
sampleapp-alpha (4) start/running, process 4168
|
||||
sampleapp-alpha (3) start/running, process 4166
|
||||
sampleapp-alpha (2) start/running, process 4164
|
||||
sampleapp-alpha (1) start/running, process 4204
|
||||
sampleapp-bravo (1) start/running, process 4589
|
||||
sampleapp-charlie (1) start/running, process 4597
|
||||
|
||||
$ foreman scale sampleapp alpha 1
|
||||
sampleapp-alpha stop/waiting
|
||||
sampleapp-alpha stop/waiting
|
||||
sampleapp-alpha stop/waiting
|
||||
|
||||
=== Good Upstart citizen
|
||||
|
||||
All Upstart commands work as expected
|
||||
|
||||
$ start sampleapp
|
||||
$ stop sampleapp
|
||||
$ restart sampleapp
|
||||
|
||||
=== Standardized Logging
|
||||
|
||||
/var/log/sampleapp/alpha.log
|
||||
/var/log/sampleapp/bravo.log
|
||||
/var/log/sampleapp/charlie.log
|
||||
|
||||
== License
|
||||
|
||||
MIT
|
||||
|
||||
== Copyright
|
||||
|
||||
(c) 2010 David Dollar
|
||||
191
Rakefile
191
Rakefile
@@ -1,14 +1,15 @@
|
||||
require "rubygems"
|
||||
require "rake"
|
||||
require "rspec"
|
||||
require "rspec/core/rake_task"
|
||||
|
||||
$:.unshift File.expand_path("../lib", __FILE__)
|
||||
require "foreman"
|
||||
|
||||
task :default => :spec
|
||||
task :release => :man
|
||||
|
||||
desc "Run all specs"
|
||||
Rspec::Core::RakeTask.new(:spec) do |t|
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.pattern = 'spec/**/*_spec.rb'
|
||||
end
|
||||
|
||||
@@ -17,44 +18,170 @@ task :rcov => "rcov:build" do
|
||||
%x{ open coverage/index.html }
|
||||
end
|
||||
|
||||
Rspec::Core::RakeTask.new("rcov:build") do |t|
|
||||
RSpec::Core::RakeTask.new("rcov:build") do |t|
|
||||
t.pattern = 'spec/**/*_spec.rb'
|
||||
t.rcov = true
|
||||
t.rcov_opts = [ "--exclude", Gem.default_dir , "--exclude", "spec" ]
|
||||
t.rcov_opts = [ "--exclude", ".bundle", "--exclude", "spec" ]
|
||||
end
|
||||
|
||||
######################################################
|
||||
desc 'Build the manual'
|
||||
task :man do
|
||||
ENV['RONN_MANUAL'] = "Foreman Manual"
|
||||
ENV['RONN_ORGANIZATION'] = "Foreman #{Foreman::VERSION}"
|
||||
sh "ronn -w -s toc -r5 --markdown man/*.ronn"
|
||||
end
|
||||
|
||||
begin
|
||||
require 'jeweler'
|
||||
Jeweler::Tasks.new do |s|
|
||||
s.name = "foreman"
|
||||
s.version = Foreman::VERSION
|
||||
desc "Commit the manual to git"
|
||||
task "man:commit" => :man do
|
||||
sh "git add README.markdown"
|
||||
sh "git commit -m 'update readme' || echo 'nothing to commit'"
|
||||
end
|
||||
|
||||
s.summary = "Process manager for applications with multiple components"
|
||||
s.description = s.summary
|
||||
s.author = "David Dollar"
|
||||
s.email = "ddollar@gmail.com"
|
||||
s.homepage = "http://github.com/ddollar/foreman"
|
||||
desc "Generate the Github docs"
|
||||
task :pages => "man:commit" do
|
||||
sh %{
|
||||
cp man/foreman.1.html /tmp/foreman.1.html
|
||||
git checkout gh-pages
|
||||
rm ./index.html
|
||||
cp /tmp/foreman.1.html ./index.html
|
||||
git add -u index.html
|
||||
git commit -m "saving man page to github docs"
|
||||
git push origin -f gh-pages
|
||||
git checkout master
|
||||
}
|
||||
end
|
||||
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.has_rdoc = false
|
||||
## dist
|
||||
|
||||
s.files = %w(Rakefile README.md) + Dir["{bin,lib,spec}/**/*"]
|
||||
s.require_path = "lib"
|
||||
require "erb"
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
|
||||
# #s.bindir = "bin"
|
||||
# s.executables = Dir["bin/*"]
|
||||
s.default_executable = "foreman"
|
||||
|
||||
s.add_development_dependency 'fakefs', '~> 0.2.1'
|
||||
s.add_development_dependency 'rake', '~> 0.8.7'
|
||||
s.add_development_dependency 'rcov', '~> 0.9.8'
|
||||
s.add_development_dependency 'rr', '~> 0.10.11'
|
||||
s.add_development_dependency 'rspec', '~> 2.0.0'
|
||||
|
||||
s.add_dependency 'thor', '~> 0.13.6'
|
||||
def assemble(source, target, perms=0644)
|
||||
FileUtils.mkdir_p(File.dirname(target))
|
||||
File.open(target, "w") do |f|
|
||||
f.puts ERB.new(File.read(source)).result(binding)
|
||||
end
|
||||
File.chmod(perms, target)
|
||||
end
|
||||
|
||||
def assemble_distribution(target_dir=Dir.pwd)
|
||||
distribution_files.each do |source|
|
||||
target = source.gsub(/^#{project_root}/, target_dir)
|
||||
FileUtils.mkdir_p(File.dirname(target))
|
||||
FileUtils.cp(source, target)
|
||||
end
|
||||
end
|
||||
|
||||
GEM_BLACKLIST = %w( bundler foreman )
|
||||
|
||||
def assemble_gems(target_dir=Dir.pwd)
|
||||
lines = %x{ bundle show }.strip.split("\n")
|
||||
raise "error running bundler" unless $?.success?
|
||||
|
||||
%x{ env BUNDLE_WITHOUT="development:test" bundle show }.split("\n").each do |line|
|
||||
if line =~ /^ \* (.*?) \((.*?)\)/
|
||||
next if GEM_BLACKLIST.include?($1)
|
||||
puts "vendoring: #{$1}-#{$2}"
|
||||
gem_dir = %x{ bundle show #{$1} }.strip
|
||||
FileUtils.mkdir_p "#{target_dir}/vendor/gems"
|
||||
%x{ cp -R "#{gem_dir}" "#{target_dir}/vendor/gems" }
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
def beta?
|
||||
Foreman::VERSION.to_s =~ /pre/
|
||||
end
|
||||
|
||||
def clean(file)
|
||||
rm file if File.exists?(file)
|
||||
end
|
||||
|
||||
def distribution_files(type=nil)
|
||||
require "foreman/distribution"
|
||||
base_files = Foreman::Distribution.files
|
||||
type_files = type ?
|
||||
Dir[File.expand_path("../dist/resources/#{type}/**/*", __FILE__)] :
|
||||
[]
|
||||
base_files.concat(type_files)
|
||||
end
|
||||
|
||||
def mkchdir(dir)
|
||||
FileUtils.mkdir_p(dir)
|
||||
Dir.chdir(dir) do |dir|
|
||||
yield(File.expand_path(dir))
|
||||
end
|
||||
end
|
||||
|
||||
def pkg(filename)
|
||||
File.expand_path("../pkg/#{filename}", __FILE__)
|
||||
end
|
||||
|
||||
def project_root
|
||||
File.dirname(__FILE__)
|
||||
end
|
||||
|
||||
def resource(name)
|
||||
File.expand_path("../dist/resources/#{name}", __FILE__)
|
||||
end
|
||||
|
||||
def s3_connect
|
||||
return if @s3_connected
|
||||
|
||||
require "aws/s3"
|
||||
|
||||
unless ENV["DAVID_RELEASE_ACCESS"] && ENV["DAVID_RELEASE_SECRET"]
|
||||
puts "please set DAVID_RELEASE_ACCESS and DAVID_RELEASE_SECRET in your environment"
|
||||
exit 1
|
||||
end
|
||||
|
||||
AWS::S3::Base.establish_connection!(
|
||||
:access_key_id => ENV["DAVID_RELEASE_ACCESS"],
|
||||
:secret_access_key => ENV["DAVID_RELEASE_SECRET"]
|
||||
)
|
||||
|
||||
@s3_connected = true
|
||||
end
|
||||
|
||||
def store(package_file, filename, bucket="assets.foreman.io")
|
||||
s3_connect
|
||||
puts "storing: #{filename}"
|
||||
AWS::S3::S3Object.store(filename, File.open(package_file), bucket, :access => :public_read)
|
||||
end
|
||||
|
||||
def tempdir
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir(dir) do
|
||||
yield(dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def version
|
||||
require "foreman/version"
|
||||
Foreman::VERSION
|
||||
end
|
||||
|
||||
Dir[File.expand_path("../dist/**/*.rake", __FILE__)].each do |rake|
|
||||
import rake
|
||||
end
|
||||
|
||||
task :changelog do
|
||||
timestamp = Time.now.utc.strftime('%m/%d/%Y')
|
||||
sha = `git log | head -1`.split(' ').last
|
||||
changelog = ["#{version} #{timestamp} #{sha}"]
|
||||
changelog << ('=' * changelog[0].length)
|
||||
changelog << ''
|
||||
|
||||
last_sha = `cat Changelog | head -1`.split(' ').last
|
||||
shortlog = `git log #{last_sha}..HEAD --pretty=format:'%s [%an]'`
|
||||
changelog << shortlog.split("\n")
|
||||
changelog.concat ['', '', '']
|
||||
|
||||
old_changelog = File.read('Changelog')
|
||||
File.open('Changelog', 'w') do |file|
|
||||
file.write(changelog.join("\n"))
|
||||
file.write(old_changelog)
|
||||
end
|
||||
rescue LoadError
|
||||
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
||||
end
|
||||
|
||||
36
bin/runner
Executable file
36
bin/runner
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
#/ Usage: runner [-d <dir>] <command>
|
||||
#/
|
||||
#/ Run a command with exec, optionally changing directory first
|
||||
|
||||
set -e
|
||||
|
||||
error() {
|
||||
echo $@ >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat $0 | grep '^#/' | cut -c4-
|
||||
exit
|
||||
}
|
||||
|
||||
while getopts ":hd:" OPT; do
|
||||
case $OPT in
|
||||
d) cd $OPTARG ;;
|
||||
h) usage ;;
|
||||
\?) error "invalid option: -$OPTARG" ;;
|
||||
:) error "option -$OPTARG requires an argument" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND-1))
|
||||
|
||||
command=$1
|
||||
|
||||
if [ "$1" == "" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
exec $1
|
||||
2
data/example/Procfile
Normal file
2
data/example/Procfile
Normal file
@@ -0,0 +1,2 @@
|
||||
ticker: ruby ./ticker $PORT
|
||||
error: ruby ./error
|
||||
2
data/example/Procfile.without_colon
Normal file
2
data/example/Procfile.without_colon
Normal file
@@ -0,0 +1,2 @@
|
||||
ticker ./ticker $PORT
|
||||
error ./error
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
puts "will error in 10s"
|
||||
sleep 10
|
||||
sleep 5
|
||||
raise "Dying"
|
||||
4
data/example/log/neverdie.log
Normal file
4
data/example/log/neverdie.log
Normal file
@@ -0,0 +1,4 @@
|
||||
tick
|
||||
tick
|
||||
./never_die:6:in `sleep': Interrupt
|
||||
from ./never_die:6
|
||||
14
data/example/ticker
Executable file
14
data/example/ticker
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
%w( SIGINT SIGTERM ).each do |signal|
|
||||
trap(signal) do
|
||||
puts "received #{signal} but i'm ignoring it!"
|
||||
end
|
||||
end
|
||||
|
||||
while true
|
||||
puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
|
||||
sleep 1
|
||||
end
|
||||
27
data/export/bluepill/master.pill.erb
Normal file
27
data/export/bluepill/master.pill.erb
Normal file
@@ -0,0 +1,27 @@
|
||||
Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
|
||||
|
||||
app.uid = "<%= user %>"
|
||||
app.gid = "<%= user %>"
|
||||
|
||||
<% engine.procfile.entries.each do |process| %>
|
||||
<% 1.upto(concurrency[process.name]) do |num| %>
|
||||
<% port = engine.port_for(process, num, options[:port]) %>
|
||||
app.process("<%= process.name %>-<%=num%>") do |process|
|
||||
process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
|
||||
|
||||
process.working_dir = "<%= engine.directory %>"
|
||||
process.daemonize = true
|
||||
process.environment = {"PORT" => "<%= port %>"}
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
|
||||
process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill -QUIT {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "<%= app %>-<%= process.name %>"
|
||||
end
|
||||
<% end %>
|
||||
<% end %>
|
||||
end
|
||||
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_root %>/<%= process.name %>-<%= num %>
|
||||
|
||||
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
|
||||
exec chpst -u <%= user %> svlogd "$LOG"
|
||||
3
data/export/runit/run.erb
Normal file
3
data/export/runit/run.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd <%= engine.directory %>
|
||||
exec chpst -u <%= user %> -e <%= process_env_directory %> <%= process.command %>
|
||||
8
data/export/upstart/master.conf.erb
Normal file
8
data/export/upstart/master.conf.erb
Normal file
@@ -0,0 +1,8 @@
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p <%= log_root %>
|
||||
chown -R <%= user %> <%= log_root %>
|
||||
EOF
|
||||
|
||||
end script
|
||||
5
data/export/upstart/process.conf.erb
Normal file
5
data/export/upstart/process.conf.erb
Normal file
@@ -0,0 +1,5 @@
|
||||
start on starting <%= app %>-<%= process.name %>
|
||||
stop on stopping <%= app %>-<%= process.name %>
|
||||
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'
|
||||
2
data/export/upstart/process_master.conf.erb
Normal file
2
data/export/upstart/process_master.conf.erb
Normal file
@@ -0,0 +1,2 @@
|
||||
start on starting <%= app %>
|
||||
stop on stopping <%= app %>
|
||||
48
dist/deb.rake
vendored
Normal file
48
dist/deb.rake
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
file pkg("/apt-#{version}/foreman-#{version}.deb") => distribution_files("deb") do |t|
|
||||
mkchdir(File.dirname(t.name)) do
|
||||
mkchdir("usr/local/foreman") do
|
||||
assemble_distribution
|
||||
assemble_gems
|
||||
assemble resource("deb/foreman"), "bin/foreman", 0755
|
||||
File.chmod 0755, "bin/runner"
|
||||
end
|
||||
|
||||
assemble resource("deb/control"), "control"
|
||||
assemble resource("deb/postinst"), "postinst"
|
||||
|
||||
sh "tar czvf data.tar.gz usr/local/foreman --owner=root --group=root"
|
||||
sh "tar czvf control.tar.gz control postinst"
|
||||
|
||||
File.open("debian-binary", "w") do |f|
|
||||
f.puts "2.0"
|
||||
end
|
||||
|
||||
deb = File.basename(t.name)
|
||||
|
||||
sh "ar -r #{t.name} debian-binary control.tar.gz data.tar.gz"
|
||||
|
||||
touch "Sources"
|
||||
sh "apt-ftparchive packages . > Packages"
|
||||
sh "gzip -c Packages > Packages.gz"
|
||||
sh "apt-ftparchive release . > Release"
|
||||
sh "gpg -abs -u 0F1B0520 -o Release.gpg Release"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Build a .deb package"
|
||||
task "deb:build" => pkg("/apt-#{version}/foreman-#{version}.deb")
|
||||
|
||||
desc "Remove build artifacts for .deb"
|
||||
task "deb:clean" do
|
||||
clean pkg("foreman-#{version}.deb")
|
||||
FileUtils.rm_rf("pkg/apt-#{version}") if Dir.exists?("pkg/apt-#{version}")
|
||||
end
|
||||
|
||||
desc "Publish .deb to S3."
|
||||
task "deb:release" => "deb:build" do |t|
|
||||
Dir["pkg/apt-#{version}/*"].each do |file|
|
||||
unless File.directory?(file)
|
||||
store file, "apt/#{File.basename(file)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
14
dist/gem.rake
vendored
Normal file
14
dist/gem.rake
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
file pkg("foreman-#{version}.gem") => distribution_files do |t|
|
||||
sh "gem build foreman.gemspec"
|
||||
sh "mv foreman-#{version}.gem #{t.name}"
|
||||
end
|
||||
|
||||
task "gem:build" => pkg("foreman-#{version}.gem")
|
||||
|
||||
task "gem:clean" do
|
||||
clean pkg("foreman-#{version}.gem")
|
||||
end
|
||||
|
||||
task "gem:release" => "gem:build" do |t|
|
||||
sh "parka push -f #{pkg("foreman-#{version}.gem")}"
|
||||
end
|
||||
14
dist/jruby.rake
vendored
Normal file
14
dist/jruby.rake
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
file pkg("foreman-#{version}-jruby.gem") => distribution_files do |t|
|
||||
sh "env PLATFORM=jruby gem build foreman.gemspec"
|
||||
sh "mv foreman-#{version}-java.gem #{t.name}"
|
||||
end
|
||||
|
||||
task "jruby:build" => pkg("foreman-#{version}-jruby.gem")
|
||||
|
||||
task "jruby:clean" do
|
||||
clean pkg("foreman-#{version}-jruby.gem")
|
||||
end
|
||||
|
||||
task "jruby:release" => "jruby:build" do |t|
|
||||
sh "parka push -f #{pkg("foreman-#{version}-jruby.gem")}"
|
||||
end
|
||||
52
dist/pkg.rake
vendored
Normal file
52
dist/pkg.rake
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
require "erb"
|
||||
|
||||
file pkg("foreman-#{version}.pkg") => distribution_files do |t|
|
||||
tempdir do |dir|
|
||||
mkchdir("foreman") do
|
||||
assemble_distribution
|
||||
assemble_gems
|
||||
assemble resource("pkg/foreman"), "bin/foreman", 0755
|
||||
end
|
||||
|
||||
kbytes = %x{ du -ks foreman | cut -f 1 }
|
||||
num_files = %x{ find foreman | wc -l }
|
||||
|
||||
mkdir_p "pkg"
|
||||
mkdir_p "pkg/Resources"
|
||||
mkdir_p "pkg/foreman-#{version}.pkg"
|
||||
|
||||
dist = File.read(resource("pkg/Distribution.erb"))
|
||||
dist = ERB.new(dist).result(binding)
|
||||
File.open("pkg/Distribution", "w") { |f| f.puts dist }
|
||||
|
||||
dist = File.read(resource("pkg/PackageInfo.erb"))
|
||||
dist = ERB.new(dist).result(binding)
|
||||
File.open("pkg/foreman-#{version}.pkg/PackageInfo", "w") { |f| f.puts dist }
|
||||
|
||||
mkdir_p "pkg/foreman-#{version}.pkg/Scripts"
|
||||
cp resource("pkg/postinstall"), "pkg/foreman-#{version}.pkg/Scripts/postinstall"
|
||||
chmod 0755, "pkg/foreman-#{version}.pkg/Scripts/postinstall"
|
||||
|
||||
sh %{ mkbom -s foreman pkg/foreman-#{version}.pkg/Bom }
|
||||
|
||||
Dir.chdir("foreman") do
|
||||
sh %{ pax -wz -x cpio . > ../pkg/foreman-#{version}.pkg/Payload }
|
||||
end
|
||||
|
||||
sh %{ pkgutil --flatten pkg foreman-#{version}.pkg }
|
||||
|
||||
cp_r "foreman-#{version}.pkg", t.name
|
||||
end
|
||||
end
|
||||
|
||||
task "pkg:build" => pkg("foreman-#{version}.pkg")
|
||||
|
||||
task "pkg:clean" do
|
||||
clean pkg("foreman-#{version}.pkg")
|
||||
end
|
||||
|
||||
task "pkg:release" => "pkg:build" do |t|
|
||||
store pkg("foreman-#{version}.pkg"), "foreman/foreman-#{version}.pkg"
|
||||
store pkg("foreman-#{version}.pkg"), "foreman/foreman-beta.pkg" if beta?
|
||||
store pkg("foreman-#{version}.pkg"), "foreman/foreman.pkg" unless beta?
|
||||
end
|
||||
12
dist/resources/deb/control
vendored
Normal file
12
dist/resources/deb/control
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
Package: foreman
|
||||
Version: <%= version %>
|
||||
Section: main
|
||||
Priority: standard
|
||||
Architecture: all
|
||||
Depends: ruby1.9.1
|
||||
Maintainer: Heroku
|
||||
Description: Manage Procfile-based applications.
|
||||
Foreman is a manager for Procfile-based applications. Its aim is to
|
||||
abstract away the details of the Procfile format, and allow you to
|
||||
either run your application directly or export it to some other
|
||||
process management format.
|
||||
18
dist/resources/deb/foreman
vendored
Normal file
18
dist/resources/deb/foreman
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env ruby1.9.1
|
||||
|
||||
# resolve bin path, ignoring symlinks
|
||||
require "pathname"
|
||||
bin_file = Pathname.new(__FILE__).realpath
|
||||
|
||||
# add locally vendored gems to libpath
|
||||
gem_dir = File.expand_path("../../vendor/gems", bin_file)
|
||||
Dir["#{gem_dir}/**/lib"].each do |libdir|
|
||||
$:.unshift libdir
|
||||
end
|
||||
|
||||
# add self to libpath
|
||||
$:.unshift File.expand_path("../../lib", bin_file)
|
||||
|
||||
require "foreman/cli"
|
||||
|
||||
Foreman::CLI.start
|
||||
30
dist/resources/deb/heroku-release-key.txt
vendored
Normal file
30
dist/resources/deb/heroku-release-key.txt
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.11 (Darwin)
|
||||
|
||||
mQENBE5SfAEBCADLp056ZgfdtAMXLWpEuL9zY+dIHIY5qLQcDmUivjHLVE4l3Bi3
|
||||
Mn570K0W9rfk7fHBPEO2XJEDdjk8Bg6mWTAeGjdfZgZaL+qO9NjqQ5QmVR+vgp7s
|
||||
yxJYlfY+JYTZvl/JiDWGhuPHSPggXILCMf3SpqWMHGPqe/3RAK+CHCNv/94uaoS4
|
||||
vi4HQT+k4sRceiM8WqkSRYSoc7rzdDejZn+InCYFfR56VeSFF4G4I6neZs/q5T9d
|
||||
Ty2i5d0gZLaX/Iqc+3Dy0vDKClc0HUQJ6ajDPuUqKLHFUpqyuwfJij60+C3GMi8K
|
||||
ckRPti31EPFVzq3GPHU+GqA+e9j84WHr4uJ5ABEBAAG0L0hlcm9rdSBSZWxlYXNl
|
||||
IEVuZ2luZWVyaW5nIDxyZWxlYXNlQGhlcm9rdS5jb20+iQE4BBMBAgAiBQJOUnwB
|
||||
AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDJJ+vgDxsFIChECAC9h4Ay
|
||||
Nx4AQFu85cjR9rijyBflPeVqi7Xhzd7IvLg2+kZSexlb2oidj7iVSMy+vy5tG9g9
|
||||
8Az/JqMCVjcZ7ltn60OGU8gIYpJqt6VmH3vfJBxXu/Sm9tym3UCYGVvMAN5Oq6yB
|
||||
HlQkQ8F3p0cW69PmF+fibkgo9RE0EYlBIt2rUHNilTGFS6vXGr5reFFp3/rRHq3k
|
||||
bixnUwFSqNujJgnBKDPwtSYKc4pMpnhuv88xEpLH7vU8NLXQZMitKQguV8XEmcsu
|
||||
43LXlsx5uVr239/XNW+h412gIHFDSzB/YuLWlVUXMfquC96z/wxMqWWZyskDNgr0
|
||||
WDdMgzK6CUfXSqQhuQENBE5SfAEBCADbnGKcXpdVauQpINQLtRnrT0BJIrIo1Yxv
|
||||
LQRb3G7RU+Eq6aHXwk9fSKa6nEv9RsmqiW874yODnr0d/DTUWMHT+jRvPHm1wlbE
|
||||
pGR1aPSo7GgkSUdaT6CVBN3JWZ2kVJGqohNoJMYbfVaWd/kpa/LiMFWzS8LfWT2K
|
||||
xiO2vIh4qBfeRCGR7s8rADCHuHJ0eibADrgqcRfdPrChB1JiYLeTdV4yRmSzJ7TM
|
||||
zWX7OVpGfIFLbCw9NeN65pI9ePs2mSPM7DYkhhKSXWMwJNXFzn1blOGiwAwKb48P
|
||||
a/QpE6TG3PQzbYyTTP0Td1XgKAHcprvbc89a/nAk3a+PJQ/MqvDzABEBAAGJAR8E
|
||||
GAECAAkFAk5SfAECGwwACgkQySfr4A8bBSD4mAgAnCT5WRiDl0259Px9Z9J9Wk8Z
|
||||
SxugDct2Yhzca4aw1Ou4cfaIFCDXzFlBzSJfqk0HoVhp9r2gzEPUCKnSjRDyxaMo
|
||||
wZCUtqigBua+z4NB4AWgeOl/2S06I2ki1K7pfl4piYcHtEThHamnhVPJ2Hi6HsHq
|
||||
mUU+8SxleHE4GCXmKkuvxelUq9jrhHikIkm1RoqFOPb9zV3WRy4YzVHQSYfHmfk0
|
||||
9kXlM/CS0sfNv2UKCX+5e6eFIZv0rdtpp6VEh0tsFmsIClY6Z9MX7bgp8MnUJpyk
|
||||
OeIzOzQgkb4aeT0Whl+EPcTeDZfqIhVBoNXupUanmWNppFcMngxfqG2NGi1vvQ==
|
||||
=aUAq
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
3
dist/resources/deb/postinst
vendored
Executable file
3
dist/resources/deb/postinst
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman
|
||||
23
dist/resources/pkg/Distribution.erb
vendored
Normal file
23
dist/resources/pkg/Distribution.erb
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-script minSpecVersion="1.000000" authoringTool="org.ruby-lang.rake" authoringToolVersion="0.8.7">
|
||||
<title>Foreman</title>
|
||||
<options customize="never" allow-external-scripts="no"/>
|
||||
<domains enable_localSystem="true"/>
|
||||
<script><![CDATA[
|
||||
function needs_git() {
|
||||
return (system.compareVersion(system.version.ProductVersion, "10.7.0") < 0);
|
||||
}
|
||||
]]></script>
|
||||
<choices-outline>
|
||||
<line choice="git"/>
|
||||
<line choice="foreman-<%= version %>"/>
|
||||
</choices-outline>
|
||||
<choice id="git" title="git" start_selected="false" start_enabled="false" selected="needs_git()" enabled="needs_git()">
|
||||
<pkg-ref id="git.pkg" />
|
||||
</choice>
|
||||
<choice id="foreman-<%= version %>" title="foreman">
|
||||
<pkg-ref id="io.foreman.installer"/>
|
||||
</choice>
|
||||
<pkg-ref id="io.foreman.installer" installKBytes="<%= kbytes %>" version="<%= version %>" auth="Root">#foreman-<%= version %>.pkg</pkg-ref>
|
||||
</installer-script>
|
||||
|
||||
7
dist/resources/pkg/PackageInfo.erb
vendored
Normal file
7
dist/resources/pkg/PackageInfo.erb
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<pkg-info format-version="2" identifier="io.foreman.installer" version="<%= version %>" install-location="/usr/local/foreman" auth="root">
|
||||
<payload installKBytes="<%= kbytes %>" numberOfFiles="<%= num_files %>"/>
|
||||
<scripts>
|
||||
<postinstall file="./postinstall"/>
|
||||
</scripts>
|
||||
</pkg-info>
|
||||
|
||||
15
dist/resources/pkg/foreman
vendored
Normal file
15
dist/resources/pkg/foreman
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
require "pathname"
|
||||
bin_file = Pathname.new(__FILE__).realpath
|
||||
|
||||
gem_dir = File.expand_path("../../vendor/gems", bin_file)
|
||||
Dir["#{gem_dir}/**/lib"].each do |libdir|
|
||||
$:.unshift libdir
|
||||
end
|
||||
|
||||
$:.unshift File.expand_path("../../lib", bin_file)
|
||||
|
||||
require "foreman/cli"
|
||||
|
||||
Foreman::CLI.start
|
||||
2
dist/resources/pkg/postinstall
vendored
Normal file
2
dist/resources/pkg/postinstall
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman
|
||||
15
dist/resources/tgz/foreman
vendored
Normal file
15
dist/resources/tgz/foreman
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "pathname"
|
||||
bin_file = Pathname.new(__FILE__).realpath
|
||||
|
||||
gem_dir = File.expand_path("../vendor/gems", bin_file)
|
||||
Dir["#{gem_dir}/**/lib"].each do |libdir|
|
||||
$:.unshift libdir
|
||||
end
|
||||
|
||||
$:.unshift File.expand_path("../lib", bin_file)
|
||||
|
||||
require "foreman/cli"
|
||||
|
||||
Foreman::CLI.start
|
||||
24
dist/tgz.rake
vendored
Normal file
24
dist/tgz.rake
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
file pkg("foreman-#{version}.tgz") => distribution_files do |t|
|
||||
tempdir do |dir|
|
||||
mkchdir("foreman") do
|
||||
assemble_distribution
|
||||
assemble_gems
|
||||
rm_rf "bin"
|
||||
assemble resource("tgz/foreman"), "foreman", 0755
|
||||
end
|
||||
|
||||
sh "tar czvf #{t.name} foreman"
|
||||
end
|
||||
end
|
||||
|
||||
task "tgz:build" => pkg("foreman-#{version}.tgz")
|
||||
|
||||
task "tgz:clean" do
|
||||
clean pkg("foreman-#{version}.tgz")
|
||||
end
|
||||
|
||||
task "tgz:release" => "tgz:build" do |t|
|
||||
store pkg("foreman-#{version}.tgz"), "foreman/foreman-#{version}.tgz"
|
||||
store pkg("foreman-#{version}.tgz"), "foreman/foreman-beta.tgz" if beta?
|
||||
store pkg("foreman-#{version}.tgz"), "foreman/foreman.tgz" unless beta?
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
neverdie ./never_die
|
||||
diealot ./die_alot
|
||||
error ./error
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
puts "sleeping for 2s then dying"
|
||||
sleep 2
|
||||
exit 0
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
while true
|
||||
puts "tick"
|
||||
$stdout.flush
|
||||
sleep 5
|
||||
end
|
||||
@@ -1,84 +1,26 @@
|
||||
# Generated by jeweler
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
||||
# -*- encoding: utf-8 -*-
|
||||
$:.unshift File.expand_path("../lib", __FILE__)
|
||||
require "foreman/version"
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{foreman}
|
||||
s.version = "0.1.0"
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = "foreman"
|
||||
gem.version = Foreman::VERSION
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["David Dollar"]
|
||||
s.date = %q{2010-05-21}
|
||||
s.default_executable = %q{foreman}
|
||||
s.description = %q{Process manager for applications with multiple components}
|
||||
s.email = %q{ddollar@gmail.com}
|
||||
s.executables = ["foreman"]
|
||||
s.extra_rdoc_files = [
|
||||
"README.rdoc"
|
||||
]
|
||||
s.files = [
|
||||
"Rakefile",
|
||||
"bin/foreman",
|
||||
"lib/foreman.rb",
|
||||
"lib/foreman/cli.rb",
|
||||
"lib/foreman/configuration.rb",
|
||||
"lib/foreman/engine.rb",
|
||||
"lib/foreman/export.rb",
|
||||
"lib/foreman/export/upstart.rb",
|
||||
"lib/foreman/process.rb",
|
||||
"spec/foreman/cli_spec.rb",
|
||||
"spec/foreman/configuration_spec.rb",
|
||||
"spec/foreman/engine_spec.rb",
|
||||
"spec/foreman/export/upstart_spec.rb",
|
||||
"spec/foreman/export_spec.rb",
|
||||
"spec/foreman/process_spec.rb",
|
||||
"spec/foreman_spec.rb",
|
||||
"spec/spec_helper.rb"
|
||||
]
|
||||
s.has_rdoc = false
|
||||
s.homepage = %q{http://github.com/ddollar/foreman}
|
||||
s.rdoc_options = ["--charset=UTF-8"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubygems_version = %q{1.3.7}
|
||||
s.summary = %q{Process manager for applications with multiple components}
|
||||
s.test_files = [
|
||||
"spec/foreman/cli_spec.rb",
|
||||
"spec/foreman/configuration_spec.rb",
|
||||
"spec/foreman/engine_spec.rb",
|
||||
"spec/foreman/export/upstart_spec.rb",
|
||||
"spec/foreman/export_spec.rb",
|
||||
"spec/foreman/process_spec.rb",
|
||||
"spec/foreman_spec.rb",
|
||||
"spec/spec_helper.rb"
|
||||
]
|
||||
gem.author = "David Dollar"
|
||||
gem.email = "ddollar@gmail.com"
|
||||
gem.homepage = "http://github.com/ddollar/foreman"
|
||||
gem.summary = "Process manager for applications with multiple components"
|
||||
|
||||
if s.respond_to? :specification_version then
|
||||
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
||||
s.specification_version = 3
|
||||
gem.description = gem.summary
|
||||
|
||||
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
||||
s.add_development_dependency(%q<fakefs>, ["~> 0.2.1"])
|
||||
s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
|
||||
s.add_development_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||
s.add_development_dependency(%q<rr>, ["~> 0.10.11"])
|
||||
s.add_development_dependency(%q<rspec>, ["~> 2.0.0"])
|
||||
s.add_runtime_dependency(%q<thor>, ["~> 0.13.6"])
|
||||
else
|
||||
s.add_dependency(%q<fakefs>, ["~> 0.2.1"])
|
||||
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
||||
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
|
||||
s.add_dependency(%q<thor>, ["~> 0.13.6"])
|
||||
end
|
||||
else
|
||||
s.add_dependency(%q<fakefs>, ["~> 0.2.1"])
|
||||
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
||||
s.add_dependency(%q<rcov>, ["~> 0.9.8"])
|
||||
s.add_dependency(%q<rr>, ["~> 0.10.11"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.0.0"])
|
||||
s.add_dependency(%q<thor>, ["~> 0.13.6"])
|
||||
gem.executables = "foreman"
|
||||
gem.files = Dir["**/*"].select { |d| d =~ %r{^(README|bin/|data/|ext/|lib/|spec/|test/)} }
|
||||
gem.files << "man/foreman.1"
|
||||
|
||||
gem.add_dependency 'term-ansicolor', '~> 1.0.7'
|
||||
gem.add_dependency 'thor', '>= 0.13.6'
|
||||
|
||||
if ENV["PLATFORM"] == "jruby"
|
||||
gem.add_dependency "posix-spawn", "~> 0.3.6"
|
||||
gem.platform = Gem::Platform.new("java")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
module Foreman
|
||||
require "foreman/version"
|
||||
|
||||
VERSION = "0.1.0"
|
||||
module Foreman
|
||||
|
||||
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
|
||||
File.expand_path("../../bin/runner", __FILE__)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,41 +1,99 @@
|
||||
require "foreman"
|
||||
require "foreman/configuration"
|
||||
require "foreman/engine"
|
||||
require "foreman/export"
|
||||
require "thor"
|
||||
require "yaml"
|
||||
|
||||
class Foreman::CLI < Thor
|
||||
|
||||
desc "start [PROCFILE]", "Run the app described in PROCFILE"
|
||||
desc "start", "Start the application"
|
||||
|
||||
def start(procfile="Procfile")
|
||||
error "#{procfile} does not exist." unless procfile_exists?(procfile)
|
||||
Foreman::Engine.new(procfile).start
|
||||
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
||||
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 :port, :type => :numeric, :aliases => "-p"
|
||||
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
||||
|
||||
class << self
|
||||
# Hackery. Take the run method away from Thor so that we can redefine it.
|
||||
def is_thor_reserved_word?(word, type)
|
||||
return false if word == 'run'
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
desc "export APP [PROCFILE] [FORMAT]", "Export the app described in PROCFILE as APP to another FORMAT"
|
||||
def start
|
||||
check_procfile!
|
||||
engine.start
|
||||
end
|
||||
|
||||
def export(app, procfile="Procfile", format="upstart")
|
||||
error "#{procfile} does not exist." unless procfile_exists?(procfile)
|
||||
desc "export FORMAT LOCATION", "Export the application to another process management format"
|
||||
|
||||
method_option :app, :type => :string, :aliases => "-a"
|
||||
method_option :log, :type => :string, :aliases => "-l"
|
||||
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
||||
method_option :port, :type => :numeric, :aliases => "-p"
|
||||
method_option :user, :type => :string, :aliases => "-u"
|
||||
method_option :template, :type => :string, :aliases => "-t"
|
||||
method_option :concurrency, :type => :string, :aliases => "-c",
|
||||
:banner => '"alpha=5,bar=3"'
|
||||
|
||||
def export(format, location=nil)
|
||||
check_procfile!
|
||||
|
||||
formatter = case format
|
||||
when "inittab" then Foreman::Export::Inittab
|
||||
when "upstart" then Foreman::Export::Upstart
|
||||
when "bluepill" then Foreman::Export::Bluepill
|
||||
when "runit" then Foreman::Export::Runit
|
||||
else error "Unknown export format: #{format}."
|
||||
end
|
||||
|
||||
formatter.new(Foreman::Engine.new(procfile)).export(app)
|
||||
formatter.new(engine).export(location, options)
|
||||
|
||||
rescue Foreman::Export::Exception => ex
|
||||
error ex.message
|
||||
end
|
||||
|
||||
desc "scale APP PROCESS AMOUNT", "Change the concurrency of a given process type"
|
||||
desc "check", "Validate your application's Procfile"
|
||||
|
||||
def scale(app, process, amount)
|
||||
config = Foreman::Configuration.new(app)
|
||||
error "No such process: #{process}." unless config.processes[process]
|
||||
config.scale(process, amount)
|
||||
def check
|
||||
error "no processes defined" unless engine.procfile.entries.length > 0
|
||||
display "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
||||
end
|
||||
|
||||
desc "run COMMAND", "Run a command using your application's environment"
|
||||
|
||||
def run(*args)
|
||||
engine.apply_environment!
|
||||
begin
|
||||
exec args.join(" ")
|
||||
rescue Errno::EACCES
|
||||
error "not executable: #{args.first}"
|
||||
rescue Errno::ENOENT
|
||||
error "command not found: #{args.first}"
|
||||
end
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def check_procfile!
|
||||
error("#{procfile} does not exist.") unless File.exist?(procfile)
|
||||
end
|
||||
|
||||
def engine
|
||||
@engine ||= Foreman::Engine.new(procfile, options)
|
||||
end
|
||||
|
||||
def procfile
|
||||
options[:procfile] || "Procfile"
|
||||
end
|
||||
|
||||
def display(message)
|
||||
puts message
|
||||
end
|
||||
|
||||
def error(message)
|
||||
puts "ERROR: #{message}"
|
||||
exit 1
|
||||
@@ -45,4 +103,10 @@ private ######################################################################
|
||||
File.exist?(procfile)
|
||||
end
|
||||
|
||||
def options
|
||||
original_options = super
|
||||
return original_options unless File.exists?(".foreman")
|
||||
defaults = YAML::load_file(".foreman") || {}
|
||||
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
require "foreman"
|
||||
|
||||
class Foreman::Configuration
|
||||
|
||||
attr_reader :app
|
||||
attr_reader :processes
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
@processes = {}
|
||||
read_initial_config
|
||||
end
|
||||
|
||||
def scale(process, amount)
|
||||
old_amount = processes[process].to_i
|
||||
processes[process] = amount.to_i
|
||||
amount = amount.to_i
|
||||
|
||||
if (old_amount < amount)
|
||||
((old_amount + 1) .. amount).each { |num| system "start #{app}-#{process} NUM=#{num}" }
|
||||
elsif (amount < old_amount)
|
||||
((amount + 1) .. old_amount).each { |num| system "stop #{app}-#{process} NUM=#{num}" }
|
||||
end
|
||||
|
||||
write
|
||||
end
|
||||
|
||||
def write
|
||||
write_file "/etc/foreman/#{app}.conf", <<-UPSTART_CONFIG
|
||||
#{app}_processes="#{processes.keys.sort.join(' ')}"
|
||||
#{processes.keys.sort.map { |k| "#{app}_#{k}=\"#{processes[k]}\"" }.join("\n")}
|
||||
UPSTART_CONFIG
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def read_initial_config
|
||||
config = File.read("/etc/foreman/#{app}.conf").split("\n").inject({}) do |accum, line|
|
||||
key, value = line.match(/^(.+?)\s*=\s*"(.+?)"\s*$/).captures
|
||||
#accum.update(parts(1) => parts(2))
|
||||
accum.update(key => value)
|
||||
end
|
||||
config["#{app}_processes"].split(" ").each do |process|
|
||||
processes[process] = config["#{app}_#{process}"].to_i
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
|
||||
def write_file(filename, contents)
|
||||
File.open(filename, "w") do |file|
|
||||
file.puts contents
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
9
lib/foreman/distribution.rb
Normal file
9
lib/foreman/distribution.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module Foreman
|
||||
module Distribution
|
||||
def self.files
|
||||
Dir[File.expand_path("../../../{bin,data,lib}/**/*", __FILE__)].select do |file|
|
||||
File.file?(file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,95 +1,224 @@
|
||||
require "foreman"
|
||||
require "foreman/process"
|
||||
require "foreman/procfile"
|
||||
require "foreman/utils"
|
||||
require "pty"
|
||||
require "tempfile"
|
||||
require "timeout"
|
||||
require "term/ansicolor"
|
||||
require "fileutils"
|
||||
require "thread"
|
||||
|
||||
class Foreman::Engine
|
||||
|
||||
attr_reader :procfile
|
||||
attr_reader :directory
|
||||
attr_reader :options
|
||||
|
||||
def initialize(procfile)
|
||||
@procfile = read_procfile(procfile)
|
||||
@directory = File.expand_path(File.dirname(procfile))
|
||||
extend Term::ANSIColor
|
||||
|
||||
COLORS = [ cyan, yellow, green, magenta, red, blue,
|
||||
intense_cyan, intense_yellow, intense_green, intense_magenta,
|
||||
intense_red, intense_blue ]
|
||||
|
||||
def initialize(procfile, options={})
|
||||
@procfile = Foreman::Procfile.new(procfile)
|
||||
@directory = options[:app_root] || File.expand_path(File.dirname(procfile))
|
||||
@options = options
|
||||
@environment = read_environment_files(options[:env])
|
||||
@output_mutex = Mutex.new
|
||||
end
|
||||
|
||||
def processes
|
||||
@processes ||= begin
|
||||
procfile.split("\n").inject({}) do |hash, line|
|
||||
next if line.strip == ""
|
||||
process = Foreman::Process.new(*line.split(" ", 2))
|
||||
hash.update(process.name => process)
|
||||
end
|
||||
end
|
||||
def self.load_env!(env_file)
|
||||
@environment = read_environment_files(env_file)
|
||||
apply_environment!
|
||||
end
|
||||
|
||||
def start
|
||||
proctitle "ruby: foreman master"
|
||||
termtitle "#{File.basename(@directory)} - foreman"
|
||||
|
||||
processes.each do |name, process|
|
||||
fork process
|
||||
end
|
||||
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
||||
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
||||
|
||||
trap("TERM") { kill_and_exit("TERM") }
|
||||
trap("INT") { kill_and_exit("INT") }
|
||||
assign_colors
|
||||
spawn_processes
|
||||
watch_for_output
|
||||
watch_for_termination
|
||||
end
|
||||
|
||||
run_loop
|
||||
def port_for(process, num, base_port=nil)
|
||||
base_port ||= 5000
|
||||
offset = procfile.process_names.index(process.name) * 100
|
||||
base_port.to_i + offset + num - 1
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def fork(process)
|
||||
pid = Process.fork do
|
||||
proctitle "ruby: foreman #{process.name}"
|
||||
def spawn_processes
|
||||
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
|
||||
|
||||
Dir.chdir directory do
|
||||
FileUtils.mkdir_p "log"
|
||||
system "#{process.command} >>log/#{process.name}.log 2>&1"
|
||||
exit $?.exitstatus || 255
|
||||
procfile.entries.each do |entry|
|
||||
reader, writer = IO.pipe
|
||||
entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
|
||||
running_processes[process.pid] = process
|
||||
readers[process] = reader
|
||||
end
|
||||
end
|
||||
|
||||
info "started with pid #{pid}", process
|
||||
running_processes[pid] = process
|
||||
end
|
||||
|
||||
def kill_and_exit(signal="TERM")
|
||||
info "termination requested"
|
||||
def base_port
|
||||
options[:port] || 5000
|
||||
end
|
||||
|
||||
def kill_all(signal="SIGTERM")
|
||||
running_processes.each do |pid, process|
|
||||
info "killing pid #{pid}", process
|
||||
Process.kill(signal, pid)
|
||||
info "sending #{signal} to pid #{pid}"
|
||||
Process.kill(signal, pid) rescue Errno::ESRCH
|
||||
end
|
||||
exit 0
|
||||
end
|
||||
|
||||
def info(message, process=nil)
|
||||
puts "[foreman] [#{Time.now.utc}] [#{process ? process.name : "system"}] #{message}"
|
||||
def terminate_gracefully
|
||||
info "sending SIGTERM to all processes"
|
||||
kill_all "SIGTERM"
|
||||
Timeout.timeout(5) { Process.waitall }
|
||||
rescue Timeout::Error
|
||||
info "sending SIGKILL to all processes"
|
||||
kill_all "SIGKILL"
|
||||
end
|
||||
|
||||
def print_info
|
||||
info "currently running processes:"
|
||||
running_processes.each do |pid, process|
|
||||
info "pid #{pid}", process
|
||||
def watch_for_output
|
||||
Thread.new do
|
||||
begin
|
||||
loop do
|
||||
rs, ws = IO.select(readers.values, [], [], 1)
|
||||
(rs || []).each do |r|
|
||||
ps, message = r.gets.split(",", 2)
|
||||
color = colors[ps.split(".").first]
|
||||
info message, ps, color
|
||||
end
|
||||
end
|
||||
rescue Exception => ex
|
||||
puts ex.message
|
||||
puts ex.backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def watch_for_termination
|
||||
pid, status = Process.wait2
|
||||
process = running_processes.delete(pid)
|
||||
info "process terminated", process.name
|
||||
terminate_gracefully
|
||||
kill_all
|
||||
rescue Errno::ECHILD
|
||||
end
|
||||
|
||||
def info(message, name="system", color=Term::ANSIColor.white)
|
||||
print color
|
||||
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
||||
print Term::ANSIColor.reset
|
||||
print message.chomp
|
||||
puts ""
|
||||
end
|
||||
|
||||
def print(message=nil)
|
||||
@output_mutex.synchronize do
|
||||
$stdout.print message
|
||||
end
|
||||
end
|
||||
|
||||
def puts(message=nil)
|
||||
@output_mutex.synchronize do
|
||||
$stdout.puts message
|
||||
end
|
||||
end
|
||||
|
||||
def error(message)
|
||||
puts "ERROR: #{message}"
|
||||
exit 1
|
||||
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 read_procfile(procfile)
|
||||
File.read(procfile)
|
||||
end
|
||||
|
||||
def run_loop
|
||||
while true
|
||||
pid, status = Process.wait2
|
||||
process = running_processes.delete(pid)
|
||||
info "exited with code #{status}", process
|
||||
fork process
|
||||
end
|
||||
def termtitle(title)
|
||||
printf("\033]0;#{title}\007")
|
||||
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
|
||||
end
|
||||
|
||||
include Env
|
||||
extend Env
|
||||
end
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
require "foreman"
|
||||
|
||||
module Foreman::Export
|
||||
class Exception < ::Exception; end
|
||||
end
|
||||
|
||||
require "foreman/export/base"
|
||||
require "foreman/export/inittab"
|
||||
require "foreman/export/upstart"
|
||||
require "foreman/export/bluepill"
|
||||
require "foreman/export/runit"
|
||||
|
||||
44
lib/foreman/export/base.rb
Normal file
44
lib/foreman/export/base.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
require "foreman/export"
|
||||
require "foreman/utils"
|
||||
|
||||
class Foreman::Export::Base
|
||||
|
||||
attr_reader :engine
|
||||
|
||||
def initialize(engine)
|
||||
@engine = engine
|
||||
end
|
||||
|
||||
def export
|
||||
raise "export method must be overridden"
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def error(message)
|
||||
raise Foreman::Export::Exception.new(message)
|
||||
end
|
||||
|
||||
def say(message)
|
||||
puts "[foreman export] %s" % message
|
||||
end
|
||||
|
||||
def export_template(exporter, file, template_root)
|
||||
if template_root && File.exist?(file_path = File.join(template_root, file))
|
||||
File.read(file_path)
|
||||
elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
|
||||
File.read(file_path)
|
||||
else
|
||||
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
|
||||
end
|
||||
end
|
||||
|
||||
def write_file(filename, contents)
|
||||
say "writing: #{filename}"
|
||||
|
||||
File.open(filename, "w") do |file|
|
||||
file.puts contents
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
28
lib/foreman/export/bluepill.rb
Normal file
28
lib/foreman/export/bluepill.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Bluepill < Foreman::Export::Base
|
||||
|
||||
def export(location, options={})
|
||||
error("Must specify a location") unless location
|
||||
|
||||
FileUtils.mkdir_p location
|
||||
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
user = options[:user] || app
|
||||
log_root = options[:log] || "/var/log/#{app}"
|
||||
template_root = options[:template]
|
||||
|
||||
Dir["#{location}/#{app}.pill"].each do |file|
|
||||
say "cleaning up: #{file}"
|
||||
FileUtils.rm(file)
|
||||
end
|
||||
|
||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
|
||||
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
|
||||
38
lib/foreman/export/inittab.rb
Normal file
38
lib/foreman/export/inittab.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Inittab < Foreman::Export::Base
|
||||
|
||||
def export(fname=nil, options={})
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
user = options[:user] || app
|
||||
log_root = options[:log] || "/var/log/#{app}"
|
||||
|
||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
|
||||
inittab = []
|
||||
inittab << "# ----- foreman #{app} processes -----"
|
||||
|
||||
engine.procfile.entries.inject(1) do |index, process|
|
||||
1.upto(concurrency[process.name]) do |num|
|
||||
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
||||
port = engine.port_for(process, num, options[:port])
|
||||
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
|
||||
index += 1
|
||||
end
|
||||
index
|
||||
end
|
||||
|
||||
inittab << "# ----- end foreman #{app} processes -----"
|
||||
|
||||
inittab = inittab.join("\n") + "\n"
|
||||
|
||||
if fname
|
||||
FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}"
|
||||
FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}"
|
||||
write_file(fname, inittab)
|
||||
else
|
||||
puts inittab
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
61
lib/foreman/export/runit.rb
Normal file
61
lib/foreman/export/runit.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Runit < Foreman::Export::Base
|
||||
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
||||
|
||||
def export(location, options={})
|
||||
error("Must specify a location") unless location
|
||||
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
user = options[:user] || app
|
||||
log_root = options[:log] || "/var/log/#{app}"
|
||||
template_root = options[:template]
|
||||
|
||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
|
||||
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(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_env_directory
|
||||
create_directory process_log_directory
|
||||
|
||||
run = ERB.new(run_template).result(binding)
|
||||
write_file "#{process_directory}/run", run
|
||||
FileUtils.chmod 0755, "#{process_directory}/run"
|
||||
|
||||
port = engine.port_for(process, num, options[:port])
|
||||
environment_variables = {'PORT' => port}.
|
||||
merge(engine.environment).
|
||||
merge(inline_variables(process.command))
|
||||
|
||||
environment_variables.each_pair do |var, env|
|
||||
write_file "#{process_env_directory}/#{var.upcase}", env
|
||||
end
|
||||
|
||||
log_run = ERB.new(log_run_template).result(binding)
|
||||
write_file "#{process_log_directory}/run", log_run
|
||||
FileUtils.chmod 0755, "#{process_log_directory}/run"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
def create_directory(location)
|
||||
say "creating: #{location}"
|
||||
FileUtils.mkdir(location)
|
||||
end
|
||||
|
||||
def inline_variables(command)
|
||||
variable_name_regex =
|
||||
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
|
||||
end
|
||||
end
|
||||
@@ -1,65 +1,42 @@
|
||||
require "foreman/configuration"
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Upstart
|
||||
class Foreman::Export::Upstart < Foreman::Export::Base
|
||||
|
||||
attr_reader :engine
|
||||
def export(location, options={})
|
||||
error("Must specify a location") unless location
|
||||
|
||||
def initialize(engine)
|
||||
@engine = engine
|
||||
end
|
||||
FileUtils.mkdir_p location
|
||||
|
||||
def export(app)
|
||||
FileUtils.mkdir_p "/etc/foreman"
|
||||
FileUtils.mkdir_p "/etc/init"
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
user = options[:user] || app
|
||||
log_root = options[:log] || "/var/log/#{app}"
|
||||
template_root = options[:template]
|
||||
|
||||
config = Foreman::Configuration.new(app)
|
||||
|
||||
write_file "/etc/init/#{app}.conf", <<-UPSTART_MASTER
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p /var/log/#{app}
|
||||
|
||||
if [ -f /etc/foreman/#{app}.conf ]; then
|
||||
source /etc/foreman/#{app}.conf
|
||||
fi
|
||||
|
||||
for process in $( echo "$#{app}_processes" ); do
|
||||
process_count_config="#{app}_$process"
|
||||
process_count=${!process_count_config}
|
||||
|
||||
for ((i=1; i<=${process_count:=1}; i+=1)); do
|
||||
start #{app}-$process NUM=$i
|
||||
done
|
||||
done
|
||||
EOF
|
||||
|
||||
end script
|
||||
UPSTART_MASTER
|
||||
|
||||
engine.processes.values.each do |process|
|
||||
write_file "/etc/init/#{app}-#{process.name}.conf", <<-UPSTART_CHILD
|
||||
instance $NUM
|
||||
stop on stopping #{app}
|
||||
respawn
|
||||
|
||||
chdir #{engine.directory}
|
||||
exec #{process.command} >>/var/log/#{app}/#{process.name}.log 2>&1
|
||||
UPSTART_CHILD
|
||||
Dir["#{location}/#{app}*.conf"].each do |file|
|
||||
say "cleaning up: #{file}"
|
||||
FileUtils.rm(file)
|
||||
end
|
||||
|
||||
engine.processes.each do |name, process|
|
||||
config.scale(name, 1)
|
||||
end
|
||||
config.write
|
||||
end
|
||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
|
||||
private ######################################################################
|
||||
master_template = export_template("upstart", "master.conf.erb", template_root)
|
||||
master_config = ERB.new(master_template).result(binding)
|
||||
write_file "#{location}/#{app}.conf", master_config
|
||||
|
||||
def write_file(filename, contents)
|
||||
File.open(filename, "w") do |file|
|
||||
file.puts contents
|
||||
process_template = export_template("upstart", "process.conf.erb", template_root)
|
||||
|
||||
engine.procfile.entries.each do |process|
|
||||
next if (conc = concurrency[process.name]) < 1
|
||||
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
||||
process_master_config = ERB.new(process_master_template).result(binding)
|
||||
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
||||
|
||||
1.upto(concurrency[process.name]) do |num|
|
||||
port = engine.port_for(process, num, options[:port])
|
||||
process_config = ERB.new(process_template).result(binding)
|
||||
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,13 +1,81 @@
|
||||
require "foreman"
|
||||
require "rubygems"
|
||||
|
||||
class Foreman::Process
|
||||
|
||||
attr_reader :name
|
||||
attr_reader :command
|
||||
attr_reader :entry
|
||||
attr_reader :num
|
||||
attr_reader :pid
|
||||
attr_reader :port
|
||||
|
||||
def initialize(name, command)
|
||||
@name = name
|
||||
@command = command
|
||||
def initialize(entry, num, port)
|
||||
@entry = entry
|
||||
@num = num
|
||||
@port = port
|
||||
end
|
||||
|
||||
def run(pipe, basedir, environment)
|
||||
with_environment(environment.merge("PORT" => port.to_s)) do
|
||||
run_process basedir, entry.command, pipe
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
"%s.%s" % [ entry.name, num ]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def jruby?
|
||||
defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java"
|
||||
end
|
||||
|
||||
def fork_with_io(command, basedir)
|
||||
reader, writer = IO.pipe
|
||||
command = replace_command_env(command)
|
||||
pid = if jruby?
|
||||
require "posix/spawn"
|
||||
POSIX::Spawn.spawn(Foreman.runner, "-d", basedir, command, {
|
||||
:out => writer, :err => writer
|
||||
})
|
||||
else
|
||||
fork do
|
||||
trap("INT", "IGNORE")
|
||||
writer.sync = true
|
||||
$stdout.reopen writer
|
||||
$stderr.reopen writer
|
||||
reader.close
|
||||
exec Foreman.runner, "-d", basedir, command
|
||||
end
|
||||
end
|
||||
[ reader, pid ]
|
||||
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)
|
||||
old_env = ENV.each_pair.inject({}) { |h,(k,v)| h.update(k => v) }
|
||||
environment.each { |k,v| ENV[k] = v }
|
||||
ret = yield
|
||||
ENV.clear
|
||||
old_env.each { |k,v| ENV[k] = v}
|
||||
ret
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
38
lib/foreman/procfile.rb
Normal file
38
lib/foreman/procfile.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
require "foreman"
|
||||
require "foreman/procfile_entry"
|
||||
|
||||
# A valid Procfile entry is captured by this regex.
|
||||
# All other lines are ignored.
|
||||
#
|
||||
# /^([A-Za-z0-9_]+):\s*(.+)$/
|
||||
#
|
||||
# $1 = name
|
||||
# $2 = command
|
||||
#
|
||||
class Foreman::Procfile
|
||||
|
||||
attr_reader :entries
|
||||
|
||||
def initialize(filename)
|
||||
@entries = parse_procfile(filename)
|
||||
end
|
||||
|
||||
def [](name)
|
||||
entries.detect { |entry| entry.name == name }
|
||||
end
|
||||
|
||||
def process_names
|
||||
entries.map(&:name)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_procfile(filename)
|
||||
File.read(filename).split("\n").map do |line|
|
||||
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
|
||||
Foreman::ProcfileEntry.new($1, $2)
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
end
|
||||
22
lib/foreman/procfile_entry.rb
Normal file
22
lib/foreman/procfile_entry.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
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
|
||||
18
lib/foreman/utils.rb
Normal file
18
lib/foreman/utils.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
5
lib/foreman/version.rb
Normal file
5
lib/foreman/version.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
module Foreman
|
||||
|
||||
VERSION = "0.37.0.pre2"
|
||||
|
||||
end
|
||||
222
man/foreman.1
Normal file
222
man/foreman.1
Normal file
@@ -0,0 +1,222 @@
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "FOREMAN" "1" "January 2012" "Foreman 0.33.1" "Foreman Manual"
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBforeman\fR \- manage Procfile\-based applications
|
||||
.
|
||||
.SH "SYNOPSIS"
|
||||
\fBforeman start [process]\fR
|
||||
.
|
||||
.br
|
||||
\fBforeman export <format> [location]\fR
|
||||
.
|
||||
.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\.
|
||||
.
|
||||
.SH "RUNNING"
|
||||
\fBforeman start\fR is used to run your application directly from the command line\.
|
||||
.
|
||||
.P
|
||||
If no additional parameters are passed, foreman will run one instance of each type of process defined in your Procfile\.
|
||||
.
|
||||
.P
|
||||
If a parameter is passed, foreman will run one instance of the specified application type\.
|
||||
.
|
||||
.P
|
||||
The following options control how the application is run:
|
||||
.
|
||||
.TP
|
||||
\fB\-c\fR, \fB\-\-concurrency\fR
|
||||
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
|
||||
.
|
||||
.TP
|
||||
\fB\-p\fR, \fB\-\-port\fR
|
||||
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
||||
.
|
||||
.SH "EXPORTING"
|
||||
\fBforeman export\fR is used to export your application to another process management format\.
|
||||
.
|
||||
.P
|
||||
An location to export can be passed as an argument\. This argument may be either required or optional depending on the export format\.
|
||||
.
|
||||
.P
|
||||
The following options control how the application is run:
|
||||
.
|
||||
.TP
|
||||
\fB\-a\fR, \fB\-\-app\fR
|
||||
Use this name rather than the application\'s root directory name as the name of the application when exporting\.
|
||||
.
|
||||
.TP
|
||||
\fB\-c\fR, \fB\-\-concurrency\fR
|
||||
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
|
||||
.
|
||||
.TP
|
||||
\fB\-l\fR, \fB\-\-log\fR
|
||||
Specify the directory to place process logs in\.
|
||||
.
|
||||
.TP
|
||||
\fB\-p\fR, \fB\-\-port\fR
|
||||
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
||||
.
|
||||
.TP
|
||||
\fB\-u\fR, \fB\-\-user\fR
|
||||
Specify the user the application should be run as\. Defaults to the app name
|
||||
.
|
||||
.SH "OPTIONS"
|
||||
These options control all modes of foreman\'s operation\.
|
||||
.
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-directory\fR
|
||||
Specify an alternate application root\. This defaults to the directory containing the Procfile\.
|
||||
.
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-env\fR
|
||||
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
|
||||
.
|
||||
.TP
|
||||
\fB\-f\fR, \fB\-\-procfile\fR
|
||||
Specify an alternate location for the application\'s Procfile\. This file\'s containing directory will be assumed to be the root directory of the application\.
|
||||
.
|
||||
.SH "EXPORT FORMATS"
|
||||
foreman currently supports the following output formats:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
bluepill
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
inittab
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
runit
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
upstart
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "INITTAB EXPORT"
|
||||
Will export a chunk of inittab\-compatible configuration:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
# \-\-\-\-\- foreman example processes \-\-\-\-\-
|
||||
EX01:4:respawn:/bin/su \- example \-c \'PORT=5000 bundle exec thin start >> /var/log/web\-1\.log 2>&1\'
|
||||
EX02:4:respawn:/bin/su \- example \-c \'PORT=5100 bundle exec rake jobs:work >> /var/log/job\-1\.log 2>&1\'
|
||||
# \-\-\-\-\- end foreman example processes \-\-\-\-\-
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "UPSTART EXPORT"
|
||||
Will create a series of upstart scripts in the location you specify\. Scripts will be structured to make the following commands valid:
|
||||
.
|
||||
.P
|
||||
\fBstart appname\fR
|
||||
.
|
||||
.P
|
||||
\fBstop appname\-processname\fR
|
||||
.
|
||||
.P
|
||||
\fBrestart appname\-processname\-3\fR
|
||||
.
|
||||
.SH "PROCFILE"
|
||||
A Procfile should contain both a name for the process and the command used to run it\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
web: bundle exec thin start
|
||||
job: bundle exec rake jobs:work
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
A process name may contain letters, numbers amd the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ foreman check
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "ENVIRONMENT"
|
||||
If a \fB\.env\fR file exists in the current directory, the default environment will be read from it\. This file should contain key/value pairs, separated by \fB=\fR, with one key/value pair per line\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
FOO=bar
|
||||
BAZ=qux
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "DEFAULT OPTIONS"
|
||||
If a \fB\.foreman\fR file exists in the current directory, default options will be read from it\. This file should be in YAML format with the long option name as keys\. Example:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
concurrency: alpha=0,bravo=1
|
||||
port: 15000
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "EXAMPLES"
|
||||
Start one instance of each process type, interleave the output on stdout:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ foreman start
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Export the application in upstart format:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ foreman export upstart /etc/init
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
Run one process type from the application defined in a specific Procfile:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ foreman start alpha \-p ~/myapp/Procfile
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "COPYRIGHT"
|
||||
Foreman is Copyright (C) 2010 David Dollar \fIhttp://daviddollar\.org\fR
|
||||
161
man/foreman.1.ronn
Normal file
161
man/foreman.1.ronn
Normal file
@@ -0,0 +1,161 @@
|
||||
foreman(1) -- manage Procfile-based applications
|
||||
================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`foreman start [process]`<br>
|
||||
`foreman export <format> [location]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**Foreman** is a manager for Procfile-based applications. Its aim is to
|
||||
abstract away the details of the Procfile format, and allow you to either run
|
||||
your application directly or export it to some other process management
|
||||
format.
|
||||
|
||||
## RUNNING
|
||||
|
||||
`foreman start` is used to run your application directly from the command line.
|
||||
|
||||
If no additional parameters are passed, foreman will run one instance of each
|
||||
type of process defined in your Procfile.
|
||||
|
||||
If a parameter is passed, foreman will run one instance of the specified
|
||||
application type.
|
||||
|
||||
The following options control how the application is run:
|
||||
|
||||
* `-c`, `--concurrency`:
|
||||
Specify the number of each process type to run. The value passed in
|
||||
should be in the format `process=num,process=num`
|
||||
|
||||
* `-p`, `--port`:
|
||||
Specify which port to use as the base for this application. Should be
|
||||
a multiple of 1000.
|
||||
|
||||
## EXPORTING
|
||||
|
||||
`foreman export` is used to export your application to another process
|
||||
management format.
|
||||
|
||||
An location to export can be passed as an argument. This argument may be
|
||||
either required or optional depending on the export format.
|
||||
|
||||
The following options control how the application is run:
|
||||
|
||||
* `-a`, `--app`:
|
||||
Use this name rather than the application's root directory name as the
|
||||
name of the application when exporting.
|
||||
|
||||
* `-c`, `--concurrency`:
|
||||
Specify the number of each process type to run. The value passed in
|
||||
should be in the format `process=num,process=num`
|
||||
|
||||
* `-l`, `--log`:
|
||||
Specify the directory to place process logs in.
|
||||
|
||||
* `-p`, `--port`:
|
||||
Specify which port to use as the base for this application. Should be
|
||||
a multiple of 1000.
|
||||
|
||||
* `-u`, `--user`:
|
||||
Specify the user the application should be run as. Defaults to the
|
||||
app name
|
||||
|
||||
## OPTIONS
|
||||
|
||||
These options control all modes of foreman's operation.
|
||||
|
||||
* `-d`, `--directory`:
|
||||
Specify an alternate application root. This defaults to the directory
|
||||
containing the Procfile.
|
||||
|
||||
* `-e`, `--env`:
|
||||
Specify an alternate environment file. You can specify more than one
|
||||
file by using: `--env file1,file2`.
|
||||
|
||||
* `-f`, `--procfile`:
|
||||
Specify an alternate location for the application's Procfile. This file's
|
||||
containing directory will be assumed to be the root directory of the
|
||||
application.
|
||||
|
||||
## EXPORT FORMATS
|
||||
|
||||
foreman currently supports the following output formats:
|
||||
|
||||
* bluepill
|
||||
|
||||
* inittab
|
||||
|
||||
* runit
|
||||
|
||||
* upstart
|
||||
|
||||
## INITTAB EXPORT
|
||||
|
||||
Will export a chunk of inittab-compatible configuration:
|
||||
|
||||
# ----- foreman example processes -----
|
||||
EX01:4:respawn:/bin/su - example -c 'PORT=5000 bundle exec thin start >> /var/log/web-1.log 2>&1'
|
||||
EX02:4:respawn:/bin/su - example -c 'PORT=5100 bundle exec rake jobs:work >> /var/log/job-1.log 2>&1'
|
||||
# ----- end foreman example processes -----
|
||||
|
||||
## UPSTART EXPORT
|
||||
|
||||
Will create a series of upstart scripts in the location you specify. Scripts
|
||||
will be structured to make the following commands valid:
|
||||
|
||||
`start appname`
|
||||
|
||||
`stop appname-processname`
|
||||
|
||||
`restart appname-processname-3`
|
||||
|
||||
## PROCFILE
|
||||
|
||||
A Procfile should contain both a name for the process and the command used
|
||||
to run it.
|
||||
|
||||
web: bundle exec thin start
|
||||
job: bundle exec rake jobs:work
|
||||
|
||||
A process name may contain letters, numbers amd the underscore character.
|
||||
You can validate your Procfile format using the `check` command:
|
||||
|
||||
$ foreman check
|
||||
|
||||
## ENVIRONMENT
|
||||
|
||||
If a `.env` file exists in the current directory, the default environment will
|
||||
be read from it. This file should contain key/value pairs, separated by `=`, with
|
||||
one key/value pair per line.
|
||||
|
||||
FOO=bar
|
||||
BAZ=qux
|
||||
|
||||
## DEFAULT OPTIONS
|
||||
|
||||
If a `.foreman` file exists in the current directory, default options will
|
||||
be read from it. This file should be in YAML format with the long option
|
||||
name as keys. Example:
|
||||
|
||||
concurrency: alpha=0,bravo=1
|
||||
port: 15000
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Start one instance of each process type, interleave the output on stdout:
|
||||
|
||||
$ foreman start
|
||||
|
||||
Export the application in upstart format:
|
||||
|
||||
$ foreman export upstart /etc/init
|
||||
|
||||
Run one process type from the application defined in a specific Procfile:
|
||||
|
||||
$ foreman start alpha -p ~/myapp/Procfile
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
Foreman is Copyright (C) 2010 David Dollar <http://daviddollar.org>
|
||||
@@ -5,9 +5,7 @@ describe "Foreman::CLI" do
|
||||
subject { Foreman::CLI.new }
|
||||
|
||||
describe "start" do
|
||||
#let(:engine) { stub_engine }
|
||||
|
||||
describe "with a non-existent Procifile" do
|
||||
describe "with a non-existent Procfile" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Procfile does not exist.") do
|
||||
dont_allow.instance_of(Foreman::Engine).start
|
||||
@@ -28,7 +26,16 @@ describe "Foreman::CLI" do
|
||||
end
|
||||
|
||||
describe "export" do
|
||||
describe "with a non-existent Procifile" do
|
||||
describe "options" do
|
||||
it "respects --env" do
|
||||
write_procfile
|
||||
write_env("envfile")
|
||||
mock.instance_of(Foreman::Export::Upstart).export("/upstart", { "env" => "envfile" })
|
||||
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
|
||||
@@ -43,7 +50,7 @@ describe "Foreman::CLI" do
|
||||
describe "with an invalid formatter" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Unknown export format: invalidformatter.") do
|
||||
subject.export("testapp", "Procfile", "invalidformatter")
|
||||
subject.export("invalidformatter")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -53,33 +60,80 @@ describe "Foreman::CLI" do
|
||||
|
||||
it "runs successfully" do
|
||||
dont_allow(subject).error
|
||||
subject.export("testapp")
|
||||
mock.instance_of(Foreman::Export::Upstart).export("/tmp/foo", {})
|
||||
subject.export("upstart", "/tmp/foo")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "scale" do
|
||||
describe "without an existing configuration" do
|
||||
it "displays an error" do
|
||||
mock_error(subject, "No such process: alpha.") do
|
||||
subject.scale("testapp", "alpha", "2")
|
||||
end
|
||||
describe "check" do
|
||||
describe "with a valid Procfile" do
|
||||
before { write_procfile }
|
||||
|
||||
it "displays the jobs" do
|
||||
mock(subject).display("valid procfile detected (alpha, bravo)")
|
||||
subject.check
|
||||
end
|
||||
end
|
||||
|
||||
describe "with an existing configuration" do
|
||||
before(:each) { write_foreman_config("testapp") }
|
||||
|
||||
it "scales a process that exists" do
|
||||
mock.instance_of(Foreman::Configuration).scale("alpha", "2")
|
||||
subject.scale("testapp", "alpha", "2")
|
||||
describe "with a blank Procfile" do
|
||||
before do
|
||||
FileUtils.touch("Procfile")
|
||||
end
|
||||
|
||||
it "errors if a process that does not exist is specified" do
|
||||
mock_error(subject, "No such process: invalidprocess.") do
|
||||
dont_allow.instance_of(Foreman::Configuration).scale
|
||||
subject.scale("testapp", "invalidprocess", "2")
|
||||
it "displays an error" do
|
||||
mock_error(subject, "no processes defined") do
|
||||
subject.check
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "run" do
|
||||
describe "with a valid Procfile" do
|
||||
before { write_procfile }
|
||||
|
||||
describe "and a command" do
|
||||
let(:command) { ["ls", "-l"] }
|
||||
|
||||
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
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
require "spec_helper"
|
||||
require "foreman/configuration"
|
||||
|
||||
describe "Foreman::Configuration" do
|
||||
subject { Foreman::Configuration.new("testapp") }
|
||||
|
||||
describe "initialize" do
|
||||
describe "without an existing config" do
|
||||
it "has no processes" do
|
||||
subject.processes.length.should == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "with an existing config" do
|
||||
it "has processes" do
|
||||
write_foreman_config("testapp")
|
||||
subject.processes["alpha"].should == 1
|
||||
subject.processes["bravo"].should == 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "scale" do
|
||||
before(:each) { write_foreman_config("testapp") }
|
||||
|
||||
it "can scale up" do
|
||||
mock(subject).system("start testapp-alpha NUM=2")
|
||||
mock(subject).system("start testapp-alpha NUM=3")
|
||||
subject.scale("alpha", 3)
|
||||
end
|
||||
|
||||
it "can scale down" do
|
||||
mock(subject).system("stop testapp-bravo NUM=2")
|
||||
subject.scale("bravo", 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "wite" do
|
||||
it "can write a configuration file" do
|
||||
subject.scale("charlie", 3)
|
||||
subject.scale("delta", 4)
|
||||
File.read("/etc/foreman/testapp.conf").should == <<-FOREMAN_CONFIG
|
||||
testapp_processes="charlie delta"
|
||||
testapp_charlie="3"
|
||||
testapp_delta="4"
|
||||
FOREMAN_CONFIG
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@ require "spec_helper"
|
||||
require "foreman/engine"
|
||||
|
||||
describe "Foreman::Engine" do
|
||||
subject { Foreman::Engine.new("Procfile") }
|
||||
subject { Foreman::Engine.new("Procfile", {}) }
|
||||
|
||||
describe "initialize" do
|
||||
describe "without an existing Procfile" do
|
||||
@@ -10,23 +10,77 @@ describe "Foreman::Engine" do
|
||||
lambda { subject }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "with a Procfile" do
|
||||
before { write_procfile }
|
||||
|
||||
it "reads the processes" do
|
||||
write_procfile
|
||||
subject.processes["alpha"].command.should == "./alpha"
|
||||
subject.processes["bravo"].command.should == "./bravo"
|
||||
subject.procfile["alpha"].command.should == "./alpha"
|
||||
subject.procfile["bravo"].command.should == "./bravo"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "start" do
|
||||
it "forks the processes" do
|
||||
write_procfile
|
||||
mock(subject).fork(subject.processes["alpha"])
|
||||
mock(subject).fork(subject.processes["bravo"])
|
||||
mock(subject).run_loop
|
||||
mock.instance_of(Foreman::Process).run_process("./alpha", is_a(IO))
|
||||
mock.instance_of(Foreman::Process).run_process("./bravo", is_a(IO))
|
||||
mock(subject).watch_for_output
|
||||
mock(subject).watch_for_termination
|
||||
subject.start
|
||||
end
|
||||
|
||||
it "handles concurrency" do
|
||||
write_procfile
|
||||
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
|
||||
mock.instance_of(Foreman::Process).run_process("./alpha", is_a(IO)).twice
|
||||
mock.instance_of(Foreman::Process).run_process("./bravo", is_a(IO)).never
|
||||
mock(engine).watch_for_output
|
||||
mock(engine).watch_for_termination
|
||||
engine.start
|
||||
end
|
||||
end
|
||||
|
||||
describe "environment" do
|
||||
before(:each) do
|
||||
write_procfile
|
||||
stub(Process).fork
|
||||
end
|
||||
|
||||
it "should read if specified" do
|
||||
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
|
||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
||||
stub(engine).info
|
||||
mock(engine).spawn_processes
|
||||
mock(engine).watch_for_termination
|
||||
engine.environment.should == {"FOO"=>"baz"}
|
||||
engine.start
|
||||
end
|
||||
|
||||
it "should read more than one if specified" do
|
||||
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
||||
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
|
||||
stub(engine).info
|
||||
mock(engine).spawn_processes
|
||||
mock(engine).watch_for_termination
|
||||
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
|
||||
engine.start
|
||||
end
|
||||
|
||||
it "should fail if specified and doesnt exist" do
|
||||
mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
|
||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
||||
end
|
||||
|
||||
it "should read .env if none specified" do
|
||||
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
|
||||
engine = Foreman::Engine.new("Procfile")
|
||||
mock(engine).spawn_processes
|
||||
mock(engine).watch_for_termination
|
||||
engine.environment.should == {"FOO"=>"qoo"}
|
||||
engine.start
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
24
spec/foreman/export/bluepill_spec.rb
Normal file
24
spec/foreman/export/bluepill_spec.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/bluepill"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Bluepill do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:engine) { Foreman::Engine.new(procfile) }
|
||||
let(:bluepill) { Foreman::Export::Bluepill.new(engine) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("bluepill") }
|
||||
before(:each) { stub(bluepill).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
bluepill.export("/tmp/init")
|
||||
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app.pill"))
|
||||
end
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
bluepill.export("/tmp/init", :concurrency => "alpha=2")
|
||||
|
||||
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app-concurrency.pill"))
|
||||
end
|
||||
end
|
||||
36
spec/foreman/export/runit_spec.rb
Normal file
36
spec/foreman/export/runit_spec.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/runit"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Runit do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
|
||||
let(:engine) { Foreman::Engine.new(procfile) }
|
||||
let(:runit) { Foreman::Export::Runit.new(engine) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("runit") }
|
||||
before(:each) { stub(runit).say }
|
||||
before(:each) { stub(FakeFS::FileUtils).chmod }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
FileUtils.mkdir_p('/tmp/init')
|
||||
runit.export('/tmp/init', :concurrency => "alpha=2,bravo=1")
|
||||
|
||||
File.read("/tmp/init/app-alpha-1/run").should == example_export_file('runit/app-alpha-1-run')
|
||||
File.read("/tmp/init/app-alpha-1/log/run").should ==
|
||||
example_export_file('runit/app-alpha-1-log-run')
|
||||
File.read("/tmp/init/app-alpha-1/env/PORT").should == "5000\n"
|
||||
File.read("/tmp/init/app-alpha-1/env/BAR").should == "baz\n"
|
||||
|
||||
File.read("/tmp/init/app-alpha-2/run").should == example_export_file('runit/app-alpha-2-run')
|
||||
File.read("/tmp/init/app-alpha-2/log/run").should ==
|
||||
example_export_file('runit/app-alpha-2-log-run')
|
||||
File.read("/tmp/init/app-alpha-2/env/PORT").should == "5001\n"
|
||||
File.read("/tmp/init/app-alpha-2/env/BAR").should == "baz\n"
|
||||
|
||||
File.read("/tmp/init/app-bravo-1/run").should == example_export_file('runit/app-bravo-1-run')
|
||||
File.read("/tmp/init/app-bravo-1/log/run").should ==
|
||||
example_export_file('runit/app-bravo-1-log-run')
|
||||
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
|
||||
end
|
||||
end
|
||||
@@ -1,2 +1,70 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/upstart"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Upstart do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:engine) { Foreman::Engine.new(procfile) }
|
||||
let(:upstart) { Foreman::Export::Upstart.new(engine) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("upstart") }
|
||||
before(:each) { stub(upstart).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
upstart.export("/tmp/init")
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-bravo.conf").should == example_export_file("upstart/app-bravo.conf")
|
||||
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf")
|
||||
end
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
upstart.export("/tmp/init", :concurrency => "alpha=2")
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("upstart/app-alpha-2.conf")
|
||||
File.exists?("/tmp/init/app-bravo-1.conf").should == false
|
||||
end
|
||||
|
||||
context "with alternate templates" do
|
||||
let(:template_root) { "/tmp/alternate" }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p template_root
|
||||
File.open("#{template_root}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
upstart.export("/tmp/init", :template => template_root)
|
||||
|
||||
File.read("/tmp/init/app.conf").should == "alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates from home dir" do
|
||||
let(:default_template_root) {File.expand_path("#{ENV['HOME']}/.foreman/templates")}
|
||||
|
||||
before do
|
||||
ENV['_FOREMAN_SPEC_HOME'] = ENV['HOME']
|
||||
ENV['HOME'] = "/home/appuser"
|
||||
FileUtils.mkdir_p default_template_root
|
||||
File.open("#{default_template_root}/master.conf.erb", "w") { |f| f.puts "default_alternate_template" }
|
||||
end
|
||||
|
||||
after do
|
||||
ENV['HOME'] = ENV.delete('_FOREMAN_SPEC_HOME')
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
upstart.export("/tmp/init")
|
||||
|
||||
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -8,4 +8,26 @@ describe Foreman do
|
||||
it { should be_a String }
|
||||
end
|
||||
|
||||
describe "::load_env!(env_file)" do
|
||||
before do
|
||||
FakeFS.activate!
|
||||
end
|
||||
|
||||
after do
|
||||
FakeFS.deactivate!
|
||||
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
|
||||
end
|
||||
|
||||
18
spec/helper_spec.rb
Normal file
18
spec/helper_spec.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
require "spec_helper"
|
||||
|
||||
describe "spec helpers" do
|
||||
describe "#preserving_env" do
|
||||
after { ENV.delete "FOO" }
|
||||
|
||||
it "should remove added environment vars" do
|
||||
preserving_env { ENV["FOO"] = "baz" }
|
||||
ENV["FOO"].should == nil
|
||||
end
|
||||
|
||||
it "should reset modified environment vars" do
|
||||
ENV["FOO"] = "bar"
|
||||
preserving_env { ENV["FOO"] = "baz"}
|
||||
ENV["FOO"].should == "bar"
|
||||
end
|
||||
end
|
||||
end
|
||||
47
spec/resources/export/bluepill/app-concurrency.pill
Normal file
47
spec/resources/export/bluepill/app-concurrency.pill
Normal file
@@ -0,0 +1,47 @@
|
||||
Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
|
||||
|
||||
app.uid = "app"
|
||||
app.gid = "app"
|
||||
|
||||
|
||||
|
||||
|
||||
app.process("alpha-1") do |process|
|
||||
process.start_command = "./alpha"
|
||||
|
||||
process.working_dir = "/tmp/app"
|
||||
process.daemonize = true
|
||||
process.environment = {"PORT" => "5000"}
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
|
||||
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill -QUIT {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "app-alpha"
|
||||
end
|
||||
|
||||
|
||||
app.process("alpha-2") do |process|
|
||||
process.start_command = "./alpha"
|
||||
|
||||
process.working_dir = "/tmp/app"
|
||||
process.daemonize = true
|
||||
process.environment = {"PORT" => "5001"}
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
|
||||
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill -QUIT {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "app-alpha"
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
44
spec/resources/export/bluepill/app.pill
Normal file
44
spec/resources/export/bluepill/app.pill
Normal file
@@ -0,0 +1,44 @@
|
||||
Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
|
||||
|
||||
app.uid = "app"
|
||||
app.gid = "app"
|
||||
|
||||
|
||||
|
||||
|
||||
app.process("alpha-1") do |process|
|
||||
process.start_command = "./alpha"
|
||||
|
||||
process.working_dir = "/tmp/app"
|
||||
process.daemonize = true
|
||||
process.environment = {"PORT" => "5000"}
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
|
||||
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill -QUIT {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "app-alpha"
|
||||
end
|
||||
|
||||
app.process("bravo-1") do |process|
|
||||
process.start_command = "./bravo"
|
||||
|
||||
process.working_dir = "/tmp/app"
|
||||
process.daemonize = true
|
||||
process.environment = {"PORT" => "5100"}
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
|
||||
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill -QUIT {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "app-bravo"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
7
spec/resources/export/runit/app-alpha-1-log-run
Normal file
7
spec/resources/export/runit/app-alpha-1-log-run
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
LOG=/var/log/app/alpha-1
|
||||
|
||||
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
|
||||
exec chpst -u app svlogd "$LOG"
|
||||
3
spec/resources/export/runit/app-alpha-1-run
Normal file
3
spec/resources/export/runit/app-alpha-1-run
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd /tmp/app
|
||||
exec chpst -u app -e /tmp/init/app-alpha-1/env ./alpha bar=baz
|
||||
7
spec/resources/export/runit/app-alpha-2-log-run
Normal file
7
spec/resources/export/runit/app-alpha-2-log-run
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
LOG=/var/log/app/alpha-2
|
||||
|
||||
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
|
||||
exec chpst -u app svlogd "$LOG"
|
||||
3
spec/resources/export/runit/app-alpha-2-run
Normal file
3
spec/resources/export/runit/app-alpha-2-run
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd /tmp/app
|
||||
exec chpst -u app -e /tmp/init/app-alpha-2/env ./alpha bar=baz
|
||||
7
spec/resources/export/runit/app-bravo-1-log-run
Normal file
7
spec/resources/export/runit/app-bravo-1-log-run
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
LOG=/var/log/app/bravo-1
|
||||
|
||||
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
|
||||
exec chpst -u app svlogd "$LOG"
|
||||
3
spec/resources/export/runit/app-bravo-1-run
Normal file
3
spec/resources/export/runit/app-bravo-1-run
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd /tmp/app
|
||||
exec chpst -u app -e /tmp/init/app-bravo-1/env ./bravo
|
||||
5
spec/resources/export/upstart/app-alpha-1.conf
Normal file
5
spec/resources/export/upstart/app-alpha-1.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
start on starting app-alpha
|
||||
stop on stopping app-alpha
|
||||
respawn
|
||||
|
||||
exec su - app -c 'cd /tmp/app; export PORT=5000; ./alpha >> /var/log/app/alpha-1.log 2>&1'
|
||||
5
spec/resources/export/upstart/app-alpha-2.conf
Normal file
5
spec/resources/export/upstart/app-alpha-2.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
start on starting app-alpha
|
||||
stop on stopping app-alpha
|
||||
respawn
|
||||
|
||||
exec su - app -c 'cd /tmp/app; export PORT=5001; ./alpha >> /var/log/app/alpha-2.log 2>&1'
|
||||
2
spec/resources/export/upstart/app-alpha.conf
Normal file
2
spec/resources/export/upstart/app-alpha.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
start on starting app
|
||||
stop on stopping app
|
||||
5
spec/resources/export/upstart/app-bravo-1.conf
Normal file
5
spec/resources/export/upstart/app-bravo-1.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
start on starting app-bravo
|
||||
stop on stopping app-bravo
|
||||
respawn
|
||||
|
||||
exec su - app -c 'cd /tmp/app; export PORT=5100; ./bravo >> /var/log/app/bravo-1.log 2>&1'
|
||||
2
spec/resources/export/upstart/app-bravo.conf
Normal file
2
spec/resources/export/upstart/app-bravo.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
start on starting app
|
||||
stop on stopping app
|
||||
8
spec/resources/export/upstart/app.conf
Normal file
8
spec/resources/export/upstart/app.conf
Normal file
@@ -0,0 +1,8 @@
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p /var/log/app
|
||||
chown -R app /var/log/app
|
||||
EOF
|
||||
|
||||
end script
|
||||
@@ -1,7 +1,9 @@
|
||||
require "fakefs/spec_helpers"
|
||||
require "rubygems"
|
||||
require "rspec"
|
||||
require "fakefs/safe"
|
||||
require "fakefs/spec_helpers"
|
||||
|
||||
$:.unshift "lib"
|
||||
$:.unshift File.expand_path("../../lib", __FILE__)
|
||||
|
||||
def mock_error(subject, message)
|
||||
mock_exit do
|
||||
@@ -10,6 +12,10 @@ def mock_error(subject, message)
|
||||
end
|
||||
end
|
||||
|
||||
def foreman(args)
|
||||
Foreman::CLI.start(args.split(" "))
|
||||
end
|
||||
|
||||
def mock_exit(&block)
|
||||
block.should raise_error(SystemExit)
|
||||
end
|
||||
@@ -22,15 +28,58 @@ def write_foreman_config(app)
|
||||
end
|
||||
end
|
||||
|
||||
def write_procfile(procfile="Procfile")
|
||||
def write_procfile(procfile="Procfile", alpha_env="")
|
||||
File.open(procfile, "w") do |file|
|
||||
file.puts "alpha ./alpha"
|
||||
file.puts "bravo ./bravo"
|
||||
file.puts "alpha: ./alpha" + " #{alpha_env}".rstrip
|
||||
file.puts "\n"
|
||||
file.puts "bravo:\t./bravo"
|
||||
end
|
||||
File.expand_path(procfile)
|
||||
end
|
||||
|
||||
def write_env(env=".env")
|
||||
File.open(env, "w") do |file|
|
||||
file.puts "FOO=bar"
|
||||
end
|
||||
end
|
||||
|
||||
Rspec.configure do |config|
|
||||
def load_export_templates_into_fakefs(type)
|
||||
FakeFS.deactivate!
|
||||
files = Dir[File.expand_path("../../data/export/#{type}/**", __FILE__)].inject({}) do |hash, file|
|
||||
hash.update(file => File.read(file))
|
||||
end
|
||||
FakeFS.activate!
|
||||
files.each do |filename, contents|
|
||||
File.open(filename, "w") do |f|
|
||||
f.puts contents
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def example_export_file(filename)
|
||||
FakeFS.deactivate!
|
||||
data = File.read(File.expand_path("../resources/export/#{filename}", __FILE__))
|
||||
FakeFS.activate!
|
||||
data
|
||||
end
|
||||
|
||||
def preserving_env
|
||||
old_env = ENV.to_hash
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
ENV.clear
|
||||
ENV.update(old_env)
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_space(s)
|
||||
s.gsub(/\n[\n\s]*/, "\n")
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.color_enabled = true
|
||||
config.order = 'rand'
|
||||
config.include FakeFS::SpecHelpers
|
||||
config.mock_with :rr
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user