Compare commits
784 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 199e7de97c | |||
|
|
defc40b911 | ||
|
|
37dcba0e33 | ||
|
|
d205e3ba2e | ||
|
|
9905334f01 | ||
|
|
dd292ed7da | ||
|
|
67ffbe2aa2 | ||
|
|
da8bba5941 | ||
|
|
2aa1cbf94f | ||
|
|
34a1163fe9 | ||
|
|
2e5f610b76 | ||
|
|
20392d98a1 | ||
|
|
e8538d9f45 | ||
|
|
4a51a2aa8a | ||
|
|
1905a7c310 | ||
|
|
134b31f599 | ||
|
|
2b97cb458a | ||
|
|
03d76d4254 | ||
|
|
133228f247 | ||
|
|
bfba2cad71 | ||
|
|
e245026f65 | ||
|
|
ffc73366b2 | ||
|
|
284503899a | ||
|
|
144be02bbd | ||
|
|
9734a2ed65 | ||
|
|
0fff148fe0 | ||
|
|
d19a9aa043 | ||
|
|
c3abaad353 | ||
|
|
b1f91d4505 | ||
|
|
0c7b8ddd79 | ||
|
|
189b751d9f | ||
|
|
619bd03bb8 | ||
|
|
fd836b46c0 | ||
|
|
2379259b33 | ||
|
|
baf842cdd4 | ||
|
|
5c06aaaa57 | ||
|
|
75b782b664 | ||
|
|
9b4bd10cdb | ||
|
|
fff82dc685 | ||
|
|
da0a9f2de3 | ||
|
|
7d77d8ff1a | ||
|
|
51e181da29 | ||
|
|
9866a341ca | ||
|
|
90848e7dea | ||
|
|
a92e24b17c | ||
|
|
0da42cf7d2 | ||
|
|
23561b963c | ||
|
|
cc51ab2cb1 | ||
|
|
403d40b277 | ||
|
|
669a920c1e | ||
|
|
d357197718 | ||
|
|
f6b57d7b92 | ||
|
|
e79588fd40 | ||
|
|
f1c2347680 | ||
|
|
80f8242b11 | ||
|
|
383276bb79 | ||
|
|
5a7692ff2c | ||
|
|
405a85bc86 | ||
|
|
bff554d554 | ||
|
|
eaed989c75 | ||
|
|
93daebbf1b | ||
|
|
17a8a316b8 | ||
|
|
9e1d590734 | ||
|
|
6611d818b1 | ||
|
|
ad4d59ae14 | ||
|
|
434f30fe42 | ||
|
|
1cb1c8812a | ||
|
|
6786f4df39 | ||
|
|
0be08a0651 | ||
|
|
3abe10e5ab | ||
|
|
66ab0f08e7 | ||
|
|
95a1d49e9d | ||
|
|
7be4375168 | ||
|
|
2ebb33e049 | ||
|
|
9fe7ddb8bd | ||
|
|
f954a42ecb | ||
|
|
169188376b | ||
|
|
5ab08c608b | ||
|
|
0b5c1f919e | ||
|
|
c94aa13b06 | ||
|
|
8d1e3a849f | ||
|
|
5ef8bbdbe3 | ||
|
|
4e84b92536 | ||
|
|
6215f8b3db | ||
|
|
8cf5896c3e | ||
|
|
5f8032cac8 | ||
|
|
9f3b903085 | ||
|
|
69d278d864 | ||
|
|
ba9167152c | ||
|
|
f31ff18191 | ||
|
|
c42110e438 | ||
|
|
26bb0ed54e | ||
|
|
44002953f6 | ||
|
|
bb2c3a2d04 | ||
|
|
88cdaacc67 | ||
|
|
faf7b3c40f | ||
|
|
21dd610eaf | ||
|
|
fe65c7510d | ||
|
|
cd2c255296 | ||
|
|
92c1909217 | ||
|
|
af57bf3d52 | ||
|
|
443994d3b5 | ||
|
|
2faa3fb6ff | ||
|
|
0d53f6bd6c | ||
|
|
1d2bcdbc56 | ||
|
|
aceea1472a | ||
|
|
44726e377e | ||
|
|
61eca5a1d8 | ||
|
|
553ac7f81f | ||
|
|
6790cf02a9 | ||
|
|
9325f2ca6d | ||
|
|
7ad41da592 | ||
|
|
8ee7b7afdf | ||
|
|
2620b90808 | ||
|
|
89bdc3ab8e | ||
|
|
60a11eb981 | ||
|
|
61c222deb8 | ||
|
|
8fe86e98c8 | ||
|
|
5c1ffdb7dc | ||
|
|
8b49256175 | ||
|
|
37d777f224 | ||
|
|
73fc059634 | ||
|
|
f7b7029cc0 | ||
|
|
3a101ec7dd | ||
|
|
6c931ea15e | ||
|
|
d173570d98 | ||
|
|
0cd41fee7f | ||
|
|
9da4e38209 | ||
|
|
5d9dfd294e | ||
|
|
8998e9a47c | ||
|
|
8238a86942 | ||
|
|
1153fb0f0c | ||
|
|
f69c755d9a | ||
|
|
9a91e5df44 | ||
|
|
64f0749d16 | ||
|
|
6b77ca1e46 | ||
|
|
8fbee31a2d | ||
|
|
7b4eabf0c5 | ||
|
|
6a44dd3fd3 | ||
|
|
e99f3173ef | ||
|
|
6c04dab649 | ||
|
|
96e26e7412 | ||
|
|
a2c03cc402 | ||
|
|
803115c0c4 | ||
|
|
a4343187ad | ||
|
|
98af1f0943 | ||
|
|
af2d4762a8 | ||
|
|
1d06124457 | ||
|
|
698e6ae092 | ||
|
|
c617ddb3b2 | ||
|
|
f6d4badcd2 | ||
|
|
f29bf49a35 | ||
|
|
e06d36b27c | ||
|
|
85e62dfbeb | ||
|
|
574e852710 | ||
|
|
68f098c1d2 | ||
|
|
e8bdafdfc1 | ||
|
|
4abd3ebedb | ||
|
|
f765436dde | ||
|
|
b5c513b4b5 | ||
|
|
ee761ff098 | ||
|
|
27c22deb6c | ||
|
|
681a9f7e61 | ||
|
|
8335a2b1ba | ||
|
|
407425ca78 | ||
|
|
6ca505b4cd | ||
|
|
612eae5e21 | ||
|
|
497b5ea1eb | ||
|
|
cd384e0d59 | ||
|
|
8921cac35b | ||
|
|
7d6de5b2a7 | ||
|
|
cc4306492e | ||
|
|
6042783e82 | ||
|
|
b7b3a9f898 | ||
|
|
8b204db2f0 | ||
|
|
15643dcb3f | ||
|
|
5f0f2f5378 | ||
|
|
c1b57b59cf | ||
|
|
21d53818f2 | ||
|
|
359d6f1c34 | ||
|
|
584f251e4a | ||
|
|
7d9c2b2ac4 | ||
|
|
fba4d9beff | ||
|
|
0bde5fdab5 | ||
|
|
ebe160d425 | ||
|
|
1beab80c1f | ||
|
|
7b270f9f4a | ||
|
|
03e5342067 | ||
|
|
b1d57426fb | ||
|
|
06e5c52f35 | ||
|
|
64ca839c0b | ||
|
|
80aebda023 | ||
|
|
5dee2281a2 | ||
|
|
efb6d2f11d | ||
|
|
ac528d3b50 | ||
|
|
cefd4e351e | ||
|
|
9849f4558a | ||
|
|
4b53b42be1 | ||
|
|
219acaf690 | ||
|
|
f8118d7b40 | ||
|
|
7fdade277c | ||
|
|
1a0943c495 | ||
|
|
4a732abd77 | ||
|
|
3e71fea777 | ||
|
|
ae7aeabb63 | ||
|
|
0a61b1a62f | ||
|
|
9901b9f924 | ||
|
|
4c2d569810 | ||
|
|
26f9c8186d | ||
|
|
24ed8946f3 | ||
|
|
83b2a9cc50 | ||
|
|
a0228b9fa0 | ||
|
|
b1a2a4a0cd | ||
|
|
7774b7f150 | ||
|
|
b01355a093 | ||
|
|
8b18143281 | ||
|
|
e06886ed57 | ||
|
|
d6a00d7262 | ||
|
|
d9d1346640 | ||
|
|
0df1a4d784 | ||
|
|
51a704939e | ||
|
|
f41cc552c7 | ||
|
|
065bbf1cd8 | ||
|
|
b628ddc608 | ||
|
|
f745b16217 | ||
|
|
a87a882e60 | ||
|
|
6ba9252d0f | ||
|
|
efd9e2119b | ||
|
|
bf9bdbf118 | ||
|
|
929a138e54 | ||
|
|
d6514b4f33 | ||
|
|
8696a36833 | ||
|
|
3ea5de42aa | ||
|
|
4a13122082 | ||
|
|
9b2987c3f0 | ||
|
|
6274f99225 | ||
|
|
0b0324fed9 | ||
|
|
32db70b778 | ||
|
|
3db1ad6fbc | ||
|
|
003b466a17 | ||
|
|
2a896a0fb5 | ||
|
|
6a8c81a38b | ||
|
|
a83dab363e | ||
|
|
53e0f4ecf9 | ||
|
|
e0fe5baf1b | ||
|
|
1aa1f15b8f | ||
|
|
fa46a605bb | ||
|
|
3077857ab7 | ||
|
|
08aa8f9d5d | ||
|
|
771489dec9 | ||
|
|
053ae8f0be | ||
|
|
af58af1c60 | ||
|
|
9075d93370 | ||
|
|
0c2e9df722 | ||
|
|
047f1ff5c4 | ||
|
|
26b54a62c5 | ||
|
|
7b85ad7c1a | ||
|
|
91a87049db | ||
|
|
f46408e8be | ||
|
|
93f04e42ac | ||
|
|
97c4582acc | ||
|
|
5689d75a87 | ||
|
|
1325b6750e | ||
|
|
638005403f | ||
|
|
55f274532f | ||
|
|
66f76c2036 | ||
|
|
865cabb525 | ||
|
|
7c3c4bc58f | ||
|
|
2dbe8c733b | ||
|
|
84352b82cc | ||
|
|
383c1f87af | ||
|
|
ce3003b026 | ||
|
|
e06f4b2f9e | ||
|
|
b721fd894e | ||
|
|
b8ea6fd4b3 | ||
|
|
dbda63263b | ||
|
|
93cdc31be0 | ||
|
|
4dfbe46690 | ||
|
|
0033f9caeb | ||
|
|
74839800a9 | ||
|
|
b75337e21e | ||
|
|
d94f941189 | ||
|
|
0b34f067cb | ||
|
|
dbe51832b0 | ||
|
|
3a2a53be95 | ||
|
|
b2bf95479e | ||
|
|
48f764e347 | ||
|
|
de62d0655e | ||
|
|
38aecff886 | ||
|
|
e4a3215257 | ||
|
|
c705b5fbef | ||
|
|
2fcb64959b | ||
|
|
69216b4c5e | ||
|
|
5d2930745a | ||
|
|
a5465bf55e | ||
|
|
8fc3d1ef24 | ||
|
|
d33e4fb0ed | ||
|
|
39b48b566f | ||
|
|
2f982ff9c7 | ||
|
|
1217ef1b56 | ||
|
|
c9943d70ec | ||
|
|
08dca57eb4 | ||
|
|
bb3377407a | ||
|
|
084b9493d1 | ||
|
|
279a251c78 | ||
|
|
a49ef286e8 | ||
|
|
0b3da59947 | ||
|
|
1f725dd68a | ||
|
|
2272f76479 | ||
|
|
cf4762071c | ||
|
|
d86b0bed1f | ||
|
|
12f825204b | ||
|
|
62c9d1db45 | ||
|
|
1127551369 | ||
|
|
8cb58b8517 | ||
|
|
d03e931b67 | ||
|
|
9ba2b32b36 | ||
|
|
cf5689a77a | ||
|
|
c23dbb79af | ||
|
|
7e55d8d3e2 | ||
|
|
d4f23d45a4 | ||
|
|
93fa1645e7 | ||
|
|
7bdada4a10 | ||
|
|
2b47d24ab7 | ||
|
|
24695348fb | ||
|
|
38b6482af5 | ||
|
|
501bc138c5 | ||
|
|
df8c05cd6c | ||
|
|
edcc4f3567 | ||
|
|
3759dbb463 | ||
|
|
b673931c05 | ||
|
|
137e43b040 | ||
|
|
79211d9bbf | ||
|
|
16d4b84a5d | ||
|
|
5ea4537046 | ||
|
|
d51433ff82 | ||
|
|
54ab74d305 | ||
|
|
e8b8f34f41 | ||
|
|
7535f1d3d8 | ||
|
|
3af0dfb4ae | ||
|
|
78547b8175 | ||
|
|
976fbc0bb0 | ||
|
|
28a9aa774f | ||
|
|
51f5ff3842 | ||
|
|
e1e18f62bb | ||
|
|
0a09117328 | ||
|
|
c3df12746f | ||
|
|
2ec6a23fb3 | ||
|
|
0d6b784de1 | ||
|
|
2dcd2c03db | ||
|
|
9d6d0bbb7d | ||
|
|
89c1314abe | ||
|
|
f33211d100 | ||
|
|
4317079bf3 | ||
|
|
c745c282c9 | ||
|
|
7dca45db57 | ||
|
|
de3c47fe21 | ||
|
|
a8a255db4a | ||
|
|
307d63b631 | ||
|
|
de54f6a5a5 | ||
|
|
0dff116340 | ||
|
|
e053dc8434 | ||
|
|
20f6ba1563 | ||
|
|
557a08ea77 | ||
|
|
8eccc819d6 | ||
|
|
347d4a0184 | ||
|
|
0147f5d284 | ||
|
|
1da034ce66 | ||
|
|
5fc7552572 | ||
|
|
083efe3ae9 | ||
|
|
9d2382a2d2 | ||
|
|
f731daae1a | ||
|
|
b7f0e3f573 | ||
|
|
e7c523dab7 | ||
|
|
9f4f15a29c | ||
|
|
b2dc89c50e | ||
|
|
389bf05800 | ||
|
|
a632a62efd | ||
|
|
2edf6e1c68 | ||
|
|
56158e881b | ||
|
|
6b5ed495d5 | ||
|
|
9652c285f1 | ||
|
|
ba171cc10d | ||
|
|
ebb191adba | ||
|
|
8c3ef1a7af | ||
|
|
cf69c31ae3 | ||
|
|
e16a35da4b | ||
|
|
2490dd2a5b | ||
|
|
2705520496 | ||
|
|
522fee5e9e | ||
|
|
c462473a25 | ||
|
|
224fe94dc2 | ||
|
|
7cb73f5c36 | ||
|
|
1537ddbcc9 | ||
|
|
b04c81dd06 | ||
|
|
ae22d34967 | ||
|
|
9a359efbf7 | ||
|
|
9632227d29 | ||
|
|
288d118ca4 | ||
|
|
576455b8d7 | ||
|
|
a04032454a | ||
|
|
29c94c785d | ||
|
|
a0a2dd9454 | ||
|
|
1b701cddf3 | ||
|
|
4080b3f1f2 | ||
|
|
84c49ae2b8 | ||
|
|
d54b46806c | ||
|
|
bc1c5e4c74 | ||
|
|
ed4a32518f | ||
|
|
e1d3955d3c | ||
|
|
c7167e1c83 | ||
|
|
db93432118 | ||
|
|
5eada6b7cc | ||
|
|
5e46b1f43a | ||
|
|
ef723f5831 | ||
|
|
52e24ef64b | ||
|
|
0b1daf1927 | ||
|
|
e0d84a56d7 | ||
|
|
7ee2edcc60 | ||
|
|
d428ac5356 | ||
|
|
5351b49fee | ||
|
|
6ebe76d8c1 | ||
|
|
7c43c672c9 | ||
|
|
41d9050ae3 | ||
|
|
09e9cefa3a | ||
|
|
85efe5c1ba | ||
|
|
c21634c04e | ||
|
|
6d4f3476f1 | ||
|
|
724812d6e3 | ||
|
|
ce6ec4848d | ||
|
|
a37a097f73 | ||
|
|
f28725bdac | ||
|
|
51eee011ce | ||
|
|
6c8c848f54 | ||
|
|
f60c4cb767 | ||
|
|
4ad49cb058 | ||
|
|
e993af2b20 | ||
|
|
d7000bccfa | ||
|
|
2995a605b4 | ||
|
|
ef280e802d | ||
|
|
8a9001842c | ||
|
|
8a8d31eb43 | ||
|
|
3e98170878 | ||
|
|
3d84de3062 | ||
|
|
9cc0afca49 | ||
|
|
7a25d3ac5a | ||
|
|
db1a5df354 | ||
|
|
e137596ce0 | ||
|
|
c9042c5aae | ||
|
|
550adc8070 | ||
|
|
fbdde3e62a | ||
|
|
e161ecb630 | ||
|
|
853a88dfbf | ||
|
|
b4cab08327 | ||
|
|
ade0005a92 | ||
|
|
241b91a0d5 | ||
|
|
047f106d48 | ||
|
|
df043e60d8 | ||
|
|
158c184f8c | ||
|
|
2ed1fe8d44 | ||
|
|
a008886bd0 | ||
|
|
c62f892ff6 | ||
|
|
d885e019b3 | ||
|
|
cfd337b44d | ||
|
|
e76f3533dc | ||
|
|
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 |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -1,5 +1,10 @@
|
||||
coverage
|
||||
example/log/*
|
||||
man/*.html
|
||||
man/*.markdown
|
||||
pkg
|
||||
/.bundle
|
||||
/.rbenv-version
|
||||
/.yardoc
|
||||
/coverage
|
||||
/example/log/*
|
||||
/man/*.html
|
||||
/man/*.markdown
|
||||
/pkg
|
||||
/tags
|
||||
/vendor
|
||||
|
||||
21
.travis.yml
Normal file
21
.travis.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
script: bundle exec rake spec
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: jruby
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
on_success: always
|
||||
on_failure: always
|
||||
urls:
|
||||
- http://dx-helper.herokuapp.com/travis
|
||||
|
||||
rvm:
|
||||
- 1.8.7
|
||||
- 1.9.2
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 2.1.0
|
||||
- jruby
|
||||
354
Changelog.md
Normal file
354
Changelog.md
Normal file
@@ -0,0 +1,354 @@
|
||||
## 0.63.0 (2013-04-15)
|
||||
|
||||
* Revert "Ensure foreman is the process group leader" [John Griffin]
|
||||
* remove posix-spawn dependency as it does not work in jruby 1.7.3 [Andrew Brown & Corey Downing]
|
||||
* Replace Foreman::Env with dotenv [Brandon Keepers]
|
||||
* [foreman-runner] fix sourcing as . is rarely in PATH [Barry Allard]
|
||||
* Fixed specs to pass. [Kentaro Kuribayashi]
|
||||
* Permit underscore for command name in Procfile. [Kentaro Kuribayashi]
|
||||
* Update man/foreman.1 [Patrick Ellis]
|
||||
* Remove tmux option from man page [Donald Plummer]
|
||||
* Prevent upstart export from deleting similarly named upstart files [Andy Morris]
|
||||
* Add MIT license text [Per Andersson]
|
||||
* use "start|stop\ on runlevel [x]" for upstart config [Nick Messick]
|
||||
|
||||
## 0.62.0 (2013-03-08)
|
||||
|
||||
* Merge pull request #334 from ged/reentrant_signal_handlers [David Dollar]
|
||||
* Merge pull request #335 from ged/20_encoding_fix [David Dollar]
|
||||
* Try to allow children to shut down gracefully [Michael Granger]
|
||||
* Add deferred signal-handling (fixes #332). [Michael Granger]
|
||||
* Fix spec encoding problem under Ruby 2.0.0. [Michael Granger]
|
||||
* add ruby 2.0 to travis [David Dollar]
|
||||
* Merge pull request #327 from patheticpat/master [David Dollar]
|
||||
* Fixed a typo in cli options description [Michael Kaiser]
|
||||
* handled by mingw now [David Dollar]
|
||||
|
||||
## 0.61.0 (2013-01-14)
|
||||
|
||||
* Fix bug in color definitons [nseo]
|
||||
* Fix for high CPU load when processes close output [Pavel Forkert]
|
||||
* Ensure foreman is the process group leader [Christos Trochalakis]
|
||||
* Don't ignore blank lines in the output [Matt Venables]
|
||||
* Add license to gemspec [petedmarsh]
|
||||
* Since JRuby 1.9 doesn't require posix/spawn, only follow that path if JRuby is loaded and running in 1.8 mode. [Adam Hutchison]
|
||||
* Remove explicit requirement on rubygems. [Cyril Rohr]
|
||||
* Dont use shared_path variable before multistage has a chance at it [Aditya Sanghi]
|
||||
* Strip Windows Line Endings [Paul Morton]
|
||||
* Fix man page: --directory is actually --root. [Evan Jones]
|
||||
* Add timeout switch to CLI [Paulo Luis Franchini Casaretto]
|
||||
* Remove expectation of double quotes around environment variables from test comparisons [Kevin McAllister]
|
||||
* Remove explicit wrapping of Shellwords.escape in double quotes [Kevin McAllister]
|
||||
|
||||
## 0.60.2 (2012-10-08)
|
||||
|
||||
* Fix for nil value on io select loop, fixes #260 [Silvio Relli]
|
||||
|
||||
## 0.60.1 (2012-10-08)
|
||||
|
||||
* sleep on select() to avoid spinning the cpu [Silvio Relli]
|
||||
|
||||
## 0.60.0 (2012-09-25)
|
||||
|
||||
* foreman run can run things from the Procfile like heroku run. [Dan Peterson]
|
||||
|
||||
## 0.59.0 (2012-09-15)
|
||||
|
||||
* Use /bin/sh instead of bash for foreman-runner [Jeremy Evans]
|
||||
|
||||
## 0.58.0 (2012-09-14)
|
||||
|
||||
* dont set HOME [David Dollar]
|
||||
* Add StandardOutPath to launchd export [Aaron Kalin]
|
||||
* Add command argument string splitting [Aaron Kalin]
|
||||
* Cleanup launchd exporter [Aaron Kalin]
|
||||
* Enable trim_mode via '-' in ERB templates [Aaron Kalin]
|
||||
* Add support for setting environment variables [Aaron Kalin]
|
||||
* foreman run should exit with the same code as its command [Omar Khan]
|
||||
* Handle multiline strings in .env file [Szymon Nowak]
|
||||
* Use path and env variables in the inittab export [Indrek Juhkam]
|
||||
* fixed the directory option [Arnaud Lachaume]
|
||||
* Add capistrano export support [Daniel Farrell]
|
||||
|
||||
## 0.57.0 (2012-08-21)
|
||||
|
||||
* fix startup checks for upstart exporter [Aditya Sanghi]
|
||||
|
||||
## 0.56.0 (2012-08-19)
|
||||
|
||||
* read .profile, not .profile.d [David Dollar]
|
||||
|
||||
## 0.55.0 (2012-08-14)
|
||||
|
||||
* use a forked process to exec a run with environment [David Dollar]
|
||||
|
||||
## 0.54.0 (2012-08-14)
|
||||
|
||||
* use Foreman::Process to extract command running [David Dollar]
|
||||
* changed to check env for bash [brntbeer]
|
||||
|
||||
## 0.53.0 (2012-07-24)
|
||||
|
||||
* put app root in $HOME [David Dollar]
|
||||
|
||||
## 0.52.0 (2012-07-24)
|
||||
|
||||
* wrap command in a runner that sources .profile.d scripts [David Dollar]
|
||||
* fix upstart export specs [David Dollar]
|
||||
* Make upstart export start/stop with network [Daniel Farrell]
|
||||
|
||||
## 0.51.0 (2012-07-11)
|
||||
|
||||
* dont try to colorize windows [David Dollar]
|
||||
|
||||
## 0.50.0 (2012-07-11)
|
||||
|
||||
* handle windows [David Dollar]
|
||||
|
||||
## 0.49.0 (2012-07-11)
|
||||
|
||||
* 1.8 compatibility [David Dollar]
|
||||
* use one pgroup for all of foreman and kill that since ruby 1.8 sucks at pgroups [David Dollar]
|
||||
* better debugging [David Dollar]
|
||||
|
||||
## 0.48.0 (2012-07-10)
|
||||
|
||||
* allow old exporter format to work, but with deprecation warning [David Dollar]
|
||||
* remove debugging code [David Dollar]
|
||||
* Merge pull request #219 from MarkDBlackwell/patch-1 [David Dollar]
|
||||
* Avoid crash by verifying the existence of SIGHUP before accessing it. [Mark D. Blackwell]
|
||||
* allow color to be forced on [David Dollar]
|
||||
* terminate gracefully if stdout goes away [David Dollar]
|
||||
* always flush output [David Dollar]
|
||||
* Merge pull request #212 from morgoth/added-version-command [David Dollar]
|
||||
* added command for displaying foreman version [Wojciech Wnętrzak]
|
||||
* Merge pull request #211 from morgoth/fixed-yaml-usage [David Dollar]
|
||||
* fixed using YAML [Wojciech Wnętrzak]
|
||||
* test on more things, but don't fail [David Dollar]
|
||||
* changelog [David Dollar]
|
||||
* 0.48.0.pre1 [David Dollar]
|
||||
* foreman doesn't work on ruby 1.8, may try to fix later [David Dollar]
|
||||
* use bash [David Dollar]
|
||||
* massive refactoring for programmatic control and stability [David Dollar]
|
||||
* Merge pull request #164 from hsume2/master [David Dollar]
|
||||
* Only run tmux specs if tmux is installed [Henry Hsu]
|
||||
* Do not assume BUNDLE_GEMFILE [Henry Hsu]
|
||||
* Add support for starting procfile in tmux session [Henry Hsu]
|
||||
|
||||
## 0.47.0 (2012-06-07)
|
||||
|
||||
* Fix multi-word argument handling in `foreman run`. [Daniel Brockman]
|
||||
* Make 'PORT=5000 foreman start' work [Koen Van der Auwera]
|
||||
* Terminate gracefully upon SIGHUP [Stefan Schüßler]
|
||||
* Set port from .env if specified [Koen Van der Auwera]
|
||||
* Updated bluepill exporter to use environment variables from .env [Aneeth]
|
||||
* Added launchd exporter [Maxwell Swadling]
|
||||
* Quote and escape environment variables in upstart templates [Matt Griffin]
|
||||
* Added list of ports to other languages to README [elf Pavlik]
|
||||
|
||||
## 0.46.0 (2012-05-02)
|
||||
|
||||
* Add Profile load/write/append API [Michael Granger]
|
||||
* Guard against missing Procfile in engine.rb [Brian Kaney]
|
||||
|
||||
## 0.45.0 (2012-04-26)
|
||||
|
||||
* create and chown log dir in upstart export. [Phil Hagelberg]
|
||||
* remove parka from dist files [David Dollar]
|
||||
|
||||
## 0.44.0 (2012-04-23)
|
||||
|
||||
* make var output order repeatable in supervisord export [David Dollar]
|
||||
* make --procfile and --app-root influence each other in a more intuitive way [David Dollar]
|
||||
* Look for .env and app_root in the same dir as the Procfile. [Phil Hagelberg]
|
||||
|
||||
## 0.43.0 (2012-04-20)
|
||||
|
||||
* wrap supervisord env vars in quotes [Raphael Randschau]
|
||||
|
||||
## 0.42.0 (2012-04-18)
|
||||
|
||||
* Move read_environment to a public class method. [Phil Hagelberg]
|
||||
* Drop parka dependency [Phil Hagelberg]
|
||||
* add group support for supervisord [Raphael Randschau]
|
||||
* fix enviroment export [Raphael Randschau]
|
||||
|
||||
## 0.41.0 (2012-03-16)
|
||||
|
||||
* replace term-ansicolor with built-in colorization [David Dollar]
|
||||
* supervisord export template [Raphael Randschau]
|
||||
|
||||
## 0.40.0 (2012-02-24)
|
||||
|
||||
* support various quoting styles in .env [David Dollar]
|
||||
* remove load_env! as it's made unnecessary by foreman run [David Dollar]
|
||||
* Provide a useful error if `foreman check` fails to find a Procfile [R. Tyler Croy]
|
||||
* update docs [David Dollar]
|
||||
|
||||
## 0.39.0 (2012-02-07)
|
||||
|
||||
* rename bin/runner to bin/foreman-runner [David Dollar]
|
||||
* fix tgz release [David Dollar]
|
||||
* bundle update hpricot [John Firebaugh]
|
||||
* touch up .pkg release tasks [David Dollar]
|
||||
|
||||
## 0.38.0 (2012-02-02)
|
||||
|
||||
* bring back single process starting [David Dollar]
|
||||
* more attempts at getting ci working with jruby [David Dollar]
|
||||
* ignore .rbenv-version [David Dollar]
|
||||
* force to binary encoding if supported [David Dollar]
|
||||
|
||||
## 0.37.2 (2012-01-29)
|
||||
|
||||
* handle directories with spaces in runner [David Dollar]
|
||||
* update docs [David Dollar]
|
||||
|
||||
## 0.37.1 (2012-01-29)
|
||||
|
||||
* use binary pipes to better handle UTF-8 data [David Dollar]
|
||||
* set up example procfile with UTF-8 item [David Dollar]
|
||||
* remove autotest [David Dollar]
|
||||
* fix up authors generation [David Dollar]
|
||||
* fix up packaging after moving tasks [David Dollar]
|
||||
* fix up changelog tasks [David Dollar]
|
||||
|
||||
## 0.37.0 (2012-01-29)
|
||||
|
||||
* put an entire line of output inside a single mutex so we don't cross the streams [David Dollar]
|
||||
* fix race condition with process termination [David Dollar]
|
||||
* allow external custom exporters [Chris Lowder]
|
||||
* fix the test for an empty string in bin/runner [Florian Apolloner]
|
||||
* ensure we have non-nil data, fixes #111 [David Dollar]
|
||||
* make sure error method exists, fixes #104 [David Dollar]
|
||||
* clean up chdir usage [David Dollar]
|
||||
* normalize platform names [David Dollar]
|
||||
* add windows support [David Dollar]
|
||||
* add jruby support [David Dollar]
|
||||
* pass basedir along to the runner script [David Dollar]
|
||||
* harden runner script [David Dollar]
|
||||
* add many missing specs [brainopia]
|
||||
* clean up fakefs usage in specs [brainopia]
|
||||
* runit creates a full path to export directory. [Fletcher Nichol]
|
||||
|
||||
## 0.36.1 (2012-01-18)
|
||||
|
||||
* 0.36.1 [David Dollar]
|
||||
* bump term-ansicolor in gemspec [David Dollar]
|
||||
|
||||
## 0.36.0 (2012-01-17)
|
||||
|
||||
* 0.36.0 [David Dollar]
|
||||
* sync the writer stream [David Dollar]
|
||||
* capture stderr as well [David Dollar]
|
||||
|
||||
## 0.35.0 (2012-01-16)
|
||||
|
||||
* update rake [David Dollar]
|
||||
* 0.35.0 [David Dollar]
|
||||
* Merge pull request #132 from Viximo/feature/concurrency [David Dollar]
|
||||
* Fix export specs [Matt Griffin]
|
||||
* Merge branch 'master' of https://github.com/michaeldwan/foreman into feature/concurrency [Matt Griffin]
|
||||
* default process concurrency is 0 when concurrency options specified, otherwise default concurrency is 1 [Michael Dwan]
|
||||
|
||||
## 0.34.1 (2012-01-16)
|
||||
|
||||
* 0.34.1 [David Dollar]
|
||||
* fix missing start desc [David Dollar]
|
||||
|
||||
## 0.34.0 (2012-01-16)
|
||||
|
||||
* 0.34.0 [David Dollar]
|
||||
* update man page [David Dollar]
|
||||
* update docs for -d [David Dollar]
|
||||
* Merge pull request #101 from ndbroadbent/foreman [David Dollar]
|
||||
* Wrap around to the first colour when all the colours are used [Craig R Webster]
|
||||
* run specs in random order [David Dollar]
|
||||
* update rspec [David Dollar]
|
||||
* pedantry [David Dollar]
|
||||
* Set executable bit on runit run scripts. [Matthijs Langenberg]
|
||||
* Merge pull request #114 from gburt/master [David Dollar]
|
||||
* add more colors [Gabriel Burt]
|
||||
* Added option to specify app_root, if executing a Procfile from a shared location [Nathan Broadbent]
|
||||
|
||||
## 0.33.1 (2012-01-16)
|
||||
|
||||
* 0.33.1 [David Dollar]
|
||||
* Merge pull request #129 from fnichol/resolve-home-template [David Dollar]
|
||||
* Expand template path under user's home directory. [Fletcher Nichol]
|
||||
|
||||
## 0.33.0 (2012-01-15)
|
||||
|
||||
* 0.33.0 [David Dollar]
|
||||
* Revert "Merge pull request #125 from brainopia/master" [David Dollar]
|
||||
|
||||
## 0.32.0 (2012-01-12)
|
||||
|
||||
* 0.32.0 [David Dollar]
|
||||
* Merge pull request #125 from brainopia/master [David Dollar]
|
||||
* Merge pull request #121 from Viximo/feature/run [David Dollar]
|
||||
* Return some whitespace that was accidentally removed [Matt Griffin]
|
||||
* Steal the run method back from Thor so that it can be used in place for exec for running commands in the foreman environment. [Matt Griffin]
|
||||
* Remove old cruft [brainopia]
|
||||
* In case someone wants to use bin/runner directly [brainopia]
|
||||
* Fix for double fork [brainopia]
|
||||
* Use ruby exec which works with escaped cmd and replaces shell [brainopia]
|
||||
* Fix foreman to work with cmds containing pipes and redirects [brainopia]
|
||||
* Add "exec" action to allow execution of arbitrary commands with the app's environment. [Matt Griffin]
|
||||
* tweak readme [David Dollar]
|
||||
|
||||
## 0.31.0 (2012-01-04)
|
||||
|
||||
* 0.31.0 [David Dollar]
|
||||
* make fork more robust [David Dollar]
|
||||
* remove unnecessary debug [David Dollar]
|
||||
* add more information when shutting down [David Dollar]
|
||||
* Merge pull request #110 from lstoll/master [David Dollar]
|
||||
* Use different port ranges for each process type [Lincoln Stoll]
|
||||
|
||||
## 0.30.1 (2011-12-23)
|
||||
|
||||
* 0.30.1 [David Dollar]
|
||||
* require thread for mutex [David Dollar]
|
||||
|
||||
## 0.30.0 (2011-12-22)
|
||||
|
||||
* 0.30.0 [David Dollar]
|
||||
* compatibility with ruby 1.8 [David Dollar]
|
||||
|
||||
## 0.29.0 (2011-12-22)
|
||||
|
||||
* 0.29.0 [David Dollar]
|
||||
* 0.28.0.pre2 [David Dollar]
|
||||
* fix pipe error [David Dollar]
|
||||
* 0.28.0.pre1 [David Dollar]
|
||||
* Merge branch 'fork' [David Dollar]
|
||||
* wip [David Dollar]
|
||||
* wip [David Dollar]
|
||||
* wip [David Dollar]
|
||||
* wip [David Dollar]
|
||||
* wip [David Dollar]
|
||||
|
||||
## 0.27.0 (2011-12-05)
|
||||
|
||||
* 0.27.0 [David Dollar]
|
||||
* add changelog [David Dollar]
|
||||
* Merge pull request #103 from csquared/load_env_from_irb [David Dollar]
|
||||
* refactor load_env to apply_environment [Chris Continanza]
|
||||
* rename load! to load_env! [Chris Continanza]
|
||||
* use ./.env as default [Chris Continanza]
|
||||
* load contents from env file [Chris Continanza]
|
||||
* refactor engine to expose env methods [Chris Continanza]
|
||||
* disable email notifications [David Dollar]
|
||||
* add travis config [David Dollar]
|
||||
|
||||
## 0.26.1 2011-12-05
|
||||
|
||||
* Merge pull request #103 from csquared/load_env_from_irb [David Dollar]
|
||||
* refactor load_env to apply_environment [Chris Continanza]
|
||||
* rename load! to load_env! [Chris Continanza]
|
||||
* use ./.env as default [Chris Continanza]
|
||||
* load contents from env file [Chris Continanza]
|
||||
* refactor engine to expose env methods [Chris Continanza]
|
||||
* disable email notifications [David Dollar]
|
||||
* add travis config [David Dollar]
|
||||
25
Gemfile
Normal file
25
Gemfile
Normal file
@@ -0,0 +1,25 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gemspec
|
||||
|
||||
platform :mingw do
|
||||
gem "win32console", "~> 1.3.0"
|
||||
end
|
||||
|
||||
platform :jruby, :ruby_18 do
|
||||
gem "posix-spawn", "~> 0.3.6"
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'aws-s3'
|
||||
gem 'rake'
|
||||
gem 'ronn'
|
||||
gem 'fakefs', '~> 0.3.2'
|
||||
gem 'rr', '~> 1.0.2'
|
||||
gem 'rspec', '~> 2.0'
|
||||
gem "simplecov", :require => false
|
||||
gem 'timecop', '0.6.1'
|
||||
gem 'yard'
|
||||
gem 'mime-types', '~> 1.25.1'
|
||||
gem 'rdiscount', '~> 1.6.8'
|
||||
end
|
||||
71
Gemfile.lock
Normal file
71
Gemfile.lock
Normal file
@@ -0,0 +1,71 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
foreman (0.63.0)
|
||||
dotenv (>= 0.7)
|
||||
thor (>= 0.13.6)
|
||||
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
aws-s3 (0.6.3)
|
||||
builder
|
||||
mime-types
|
||||
xml-simple
|
||||
builder (3.2.2)
|
||||
diff-lcs (1.2.5)
|
||||
docile (1.1.3)
|
||||
dotenv (0.10.0)
|
||||
fakefs (0.3.2)
|
||||
hpricot (0.8.6)
|
||||
hpricot (0.8.6-java)
|
||||
mime-types (1.25.1)
|
||||
multi_json (1.9.0)
|
||||
mustache (0.99.5)
|
||||
posix-spawn (0.3.8)
|
||||
rake (10.1.1)
|
||||
rdiscount (1.6.8)
|
||||
ronn (0.7.3)
|
||||
hpricot (>= 0.8.2)
|
||||
mustache (>= 0.7.0)
|
||||
rdiscount (>= 1.5.8)
|
||||
rr (1.0.5)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-core (2.14.8)
|
||||
rspec-expectations (2.14.5)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.6)
|
||||
simplecov (0.8.2)
|
||||
docile (~> 1.1.0)
|
||||
multi_json
|
||||
simplecov-html (~> 0.8.0)
|
||||
simplecov-html (0.8.0)
|
||||
thor (0.18.1)
|
||||
timecop (0.6.1)
|
||||
win32console (1.3.2-x86-mingw32)
|
||||
xml-simple (1.1.3)
|
||||
yard (0.8.7.3)
|
||||
|
||||
PLATFORMS
|
||||
java
|
||||
ruby
|
||||
x86-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
aws-s3
|
||||
fakefs (~> 0.3.2)
|
||||
foreman!
|
||||
mime-types (~> 1.25.1)
|
||||
posix-spawn (~> 0.3.6)
|
||||
rake
|
||||
rdiscount (~> 1.6.8)
|
||||
ronn
|
||||
rr (~> 1.0.2)
|
||||
rspec (~> 2.0)
|
||||
simplecov
|
||||
timecop (= 0.6.1)
|
||||
win32console (~> 1.3.0)
|
||||
yard
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 David Dollar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Foreman
|
||||
|
||||
Manage Procfile-based applications
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>If you have...</th>
|
||||
<th>Install with...</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ruby (MRI, JRuby, Windows)</td>
|
||||
<td><pre>$ gem install foreman</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mac OS X</td>
|
||||
<td><a href="http://assets.foreman.io/foreman/foreman.pkg">foreman.pkg</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Getting Started
|
||||
|
||||
* http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
|
||||
|
||||
## Documentation
|
||||
|
||||
* [man page](http://ddollar.github.com/foreman)
|
||||
* [wiki](http://github.com/ddollar/foreman/wiki)
|
||||
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
||||
|
||||
## Ports
|
||||
|
||||
* [shoreman](https://github.com/hecticjeff/shoreman) - shell
|
||||
* [honcho](https://github.com/nickstenning/honcho) - python
|
||||
* [norman](https://github.com/josh/norman) - node.js
|
||||
* [forego](https://github.com/ddollar/forego) - Go
|
||||
|
||||
## Authors
|
||||
|
||||
#### Created and maintained by
|
||||
David Dollar
|
||||
|
||||
#### Patches contributed by
|
||||
[Contributor List](https://github.com/ddollar/foreman/contributors)
|
||||
|
||||
## License
|
||||
|
||||
Foreman is licensed under the MIT license.
|
||||
|
||||
See LICENSE for the full license text.
|
||||
85
README.rdoc
85
README.rdoc
@@ -1,85 +0,0 @@
|
||||
= Foreman
|
||||
|
||||
== Procfile
|
||||
|
||||
alpha ./bin/alpha
|
||||
bravo ./bin/bravo some args
|
||||
charlie ./bin/charlie -n 5
|
||||
|
||||
== Development mode
|
||||
|
||||
=== Running
|
||||
|
||||
Log files will be output to standard out, colorized to aid in visual separation.
|
||||
|
||||
$ foreman start
|
||||
[01:27:08] [alpha] started with pid 4393
|
||||
[01:27:08] [bravo] started with pid 4394
|
||||
[01:27:08] [charlie] started with pid 4395
|
||||
[01:27:08] [bravo] initializing...
|
||||
[01:27:08] [bravo] complete
|
||||
|
||||
=== Using Screen
|
||||
|
||||
Launch the processes in a screen session in indivudal windows
|
||||
|
||||
$ foreman screen
|
||||
|
||||
== Single Process Execution
|
||||
|
||||
$ foreman execute alpha
|
||||
|
||||
== Exporting to 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
|
||||
71
Rakefile
71
Rakefile
@@ -1,73 +1,8 @@
|
||||
require "rake"
|
||||
require "rspec"
|
||||
require "rspec/core/rake_task"
|
||||
|
||||
$:.unshift File.expand_path("../lib", __FILE__)
|
||||
require "foreman"
|
||||
|
||||
task :default => :spec
|
||||
task :release => :man
|
||||
require "bundler/setup"
|
||||
|
||||
desc "Run all specs"
|
||||
Rspec::Core::RakeTask.new(:spec) do |t|
|
||||
t.pattern = 'spec/**/*_spec.rb'
|
||||
end
|
||||
|
||||
desc "Generate RCov code coverage report"
|
||||
task :rcov => "rcov:build" do
|
||||
%x{ open coverage/index.html }
|
||||
end
|
||||
|
||||
Rspec::Core::RakeTask.new("rcov:build") do |t|
|
||||
t.pattern = 'spec/**/*_spec.rb'
|
||||
t.rcov = true
|
||||
t.rcov_opts = [ "--exclude", Gem.default_dir , "--exclude", "spec" ]
|
||||
end
|
||||
|
||||
desc 'Build the manual'
|
||||
task :man do
|
||||
require 'ronn'
|
||||
ENV['RONN_MANUAL'] = "Foreman Manual"
|
||||
ENV['RONN_ORGANIZATION'] = "Foreman #{Foreman::VERSION}"
|
||||
sh "ronn -w -s toc -r5 --markdown man/*.ronn"
|
||||
sh "git add man/*.?"
|
||||
sh "git commit -m \"updating man pages\""
|
||||
end
|
||||
|
||||
######################################################
|
||||
|
||||
begin
|
||||
require 'jeweler'
|
||||
Jeweler::Tasks.new do |s|
|
||||
s.name = "foreman"
|
||||
s.version = Foreman::VERSION
|
||||
|
||||
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"
|
||||
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.has_rdoc = false
|
||||
|
||||
s.files = %w(Rakefile README.md) + Dir["{bin,lib,spec}/**/*"]
|
||||
s.require_path = "lib"
|
||||
|
||||
# #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 'term-ansicolor', '~> 1.0.5'
|
||||
s.add_dependency 'thor', '~> 0.13.6'
|
||||
end
|
||||
Jeweler::GemcutterTasks.new
|
||||
rescue LoadError
|
||||
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
||||
Dir[File.expand_path("../tasks/*.rake", __FILE__)].each do |task|
|
||||
load task
|
||||
end
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Autotest.add_discovery { "rspec2" }
|
||||
41
bin/foreman-runner
Executable file
41
bin/foreman-runner
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
#/ Usage: foreman-runner [-d <dir>] [-p] <command> [<args>...]
|
||||
#/
|
||||
#/ Run a command with exec, optionally changing directory first
|
||||
|
||||
set -e
|
||||
|
||||
error() {
|
||||
echo $@ >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat $0 | grep '^#/' | cut -c4-
|
||||
exit
|
||||
}
|
||||
|
||||
read_profile=""
|
||||
|
||||
while getopts ":hd:p" OPT; do
|
||||
case $OPT in
|
||||
d) cd "$OPTARG" ;;
|
||||
p) read_profile="1" ;;
|
||||
h) usage ;;
|
||||
\?) error "invalid option: -$OPTARG" ;;
|
||||
:) error "option -$OPTARG requires an argument" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND-1))
|
||||
|
||||
[ -z "$1" ] && usage
|
||||
|
||||
if [ "$read_profile" = "1" ]; then
|
||||
if [ -f .profile ]; then
|
||||
. ./.profile
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
8
bin/taskman
Executable file
8
bin/taskman
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path("../../lib", __FILE__)
|
||||
|
||||
require "foreman/cli"
|
||||
|
||||
Foreman::CLI.engine_class = Foreman::TmuxEngine
|
||||
Foreman::CLI.start
|
||||
2
data/example/.profile.d/foo.sh
Normal file
2
data/example/.profile.d/foo.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
export FOO=bar
|
||||
4
data/example/Procfile
Normal file
4
data/example/Procfile
Normal file
@@ -0,0 +1,4 @@
|
||||
ticker: ruby ./ticker $PORT
|
||||
error: ruby ./error
|
||||
utf8: ruby ./utf8
|
||||
spawner: ./spawner
|
||||
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 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/spawnee
Executable file
14
data/example/spawnee
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
NAME="$1"
|
||||
|
||||
sigterm() {
|
||||
echo "$NAME: got sigterm"
|
||||
}
|
||||
|
||||
#trap sigterm SIGTERM
|
||||
|
||||
while true; do
|
||||
echo "$NAME: ping $$"
|
||||
sleep 1
|
||||
done
|
||||
7
data/example/spawner
Executable file
7
data/example/spawner
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
./spawnee A &
|
||||
./spawnee B &
|
||||
./spawnee C &
|
||||
|
||||
wait
|
||||
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
|
||||
11
data/example/utf8
Executable file
11
data/example/utf8
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env ruby
|
||||
# encoding: BINARY
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
while true
|
||||
puts "\u65e5\u672c\u8a9e\u6587\u5b57\u5217"
|
||||
puts "\u0915\u0932\u094d\u0907\u0928\u0643\u0637\u0628\u041a\u0430\u043b\u0438\u043d\u0430"
|
||||
puts "\xff\x03"
|
||||
sleep 1
|
||||
end
|
||||
28
data/export/bluepill/master.pill.erb
Normal file
28
data/export/bluepill/master.pill.erb
Normal file
@@ -0,0 +1,28 @@
|
||||
Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
|
||||
|
||||
app.uid = "<%= user %>"
|
||||
app.gid = "<%= user %>"
|
||||
|
||||
<% engine.each_process do |name, process| %>
|
||||
<% 1.upto(engine.formation[name]) do |num| %>
|
||||
<% port = engine.port_for(process, num) %>
|
||||
app.process("<%= name %>-<%= num %>") do |process|
|
||||
process.start_command = "<%= process.command %>"
|
||||
|
||||
process.working_dir = "<%= engine.root %>"
|
||||
process.daemonize = true
|
||||
process.environment = <%= engine.env.merge("PORT" => port.to_s).inspect %>
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
process.stop_grace_time = 45.seconds
|
||||
|
||||
process.stdout = process.stderr = "<%= log %>/<%= app %>-<%= name %>-<%= num %>.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "<%= app %>-<%= name %>"
|
||||
end
|
||||
<% end %>
|
||||
<% end %>
|
||||
end
|
||||
14
data/export/daemon/master.conf.erb
Normal file
14
data/export/daemon/master.conf.erb
Normal file
@@ -0,0 +1,14 @@
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p <%= log %>
|
||||
chown -R <%= user %> <%= log %>
|
||||
mkdir -p <%= run %>
|
||||
chown -R <%= user %> <%= run %>
|
||||
EOF
|
||||
|
||||
end script
|
||||
|
||||
start on runlevel [2345]
|
||||
|
||||
stop on runlevel [016]
|
||||
8
data/export/daemon/process.conf.erb
Normal file
8
data/export/daemon/process.conf.erb
Normal file
@@ -0,0 +1,8 @@
|
||||
start on starting <%= app %>-<%= name.gsub('_', '-') %>
|
||||
stop on stopping <%= app %>-<%= name.gsub('_', '-') %>
|
||||
respawn
|
||||
|
||||
env PORT=<%= port %><% engine.env.each_pair do |var, env| %>
|
||||
env <%= var.upcase %>=<%= env %><% end %>
|
||||
|
||||
exec start-stop-daemon --start --chuid <%= user %> --chdir <%= engine.root %> --make-pidfile --pidfile <%= run %>/<%= app %>-<%= name %>-<%= num %>.pid --exec <%= executable %><%= arguments %> >> <%= log %>/<%= app %>-<%= name %>-<%= num %>.log 2>&1
|
||||
2
data/export/daemon/process_master.conf.erb
Normal file
2
data/export/daemon/process_master.conf.erb
Normal file
@@ -0,0 +1,2 @@
|
||||
start on starting <%= app %>
|
||||
stop on stopping <%= app %>
|
||||
33
data/export/launchd/launchd.plist.erb
Normal file
33
data/export/launchd/launchd.plist.erb
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string><%= "#{app}-#{name}-#{num}" %></string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<%- engine.env.merge("PORT" => port).each_pair do |var,env| -%>
|
||||
<key><%= var.upcase %></key>
|
||||
<string><%= env %></string>
|
||||
<%- end -%>
|
||||
</dict>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<%- command_args.each do |command| -%>
|
||||
<string><%= command %></string>
|
||||
<%- end -%>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
|
||||
<key>UserName</key>
|
||||
<string><%= user %></string>
|
||||
<key>WorkingDirectory</key>
|
||||
<string><%= engine.root %></string>
|
||||
</dict>
|
||||
</plist>
|
||||
7
data/export/runit/log/run.erb
Normal file
7
data/export/runit/log/run.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
LOG=<%= log %>/<%= name %>-<%= num %>
|
||||
|
||||
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
|
||||
exec chpst -u <%= user %> svlogd "$LOG"
|
||||
3
data/export/runit/run.erb
Normal file
3
data/export/runit/run.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd <%= engine.root %>
|
||||
exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %>
|
||||
27
data/export/supervisord/app.conf.erb
Normal file
27
data/export/supervisord/app.conf.erb
Normal file
@@ -0,0 +1,27 @@
|
||||
<%
|
||||
app_names = []
|
||||
engine.each_process do |name, process|
|
||||
1.upto(engine.formation[name]) do |num|
|
||||
port = engine.port_for(process, num)
|
||||
full_name = "#{app}-#{name}-#{num}"
|
||||
environment = engine.env.merge("PORT" => port.to_s).map do |key, value|
|
||||
"#{key}=#{shell_quote(value)}"
|
||||
end
|
||||
app_names << full_name
|
||||
%>
|
||||
[program:<%= full_name %>]
|
||||
command=<%= process.command %>
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopsignal=QUIT
|
||||
stdout_logfile=<%= log %>/<%= name %>-<%= num %>.log
|
||||
stderr_logfile=<%= log %>/<%= name %>-<%= num %>.error.log
|
||||
user=<%= user %>
|
||||
directory=<%= engine.root %>
|
||||
environment=<%= environment.join(',') %><%
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
[group:<%= app %>]
|
||||
programs=<%= app_names.join(',') %>
|
||||
6
data/export/systemd/master.target.erb
Normal file
6
data/export/systemd/master.target.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
[Unit]
|
||||
StopWhenUnneeded=true
|
||||
Wants=<%= process_master_names.join(' ') %>
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
15
data/export/systemd/process.service.erb
Normal file
15
data/export/systemd/process.service.erb
Normal file
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
StopWhenUnneeded=true
|
||||
|
||||
[Service]
|
||||
User=<%= user %>
|
||||
WorkingDirectory=<%= engine.root %>
|
||||
Environment=PORT=<%= port %><% engine.env.each_pair do |var,env| %>
|
||||
Environment=<%= var.upcase %>=<%= env %><% end %>
|
||||
ExecStart=/bin/bash -lc '<%= process.command %>'
|
||||
Restart=always
|
||||
StandardInput=null
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=%n
|
||||
KillMode=process
|
||||
3
data/export/systemd/process_master.target.erb
Normal file
3
data/export/systemd/process_master.target.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
[Unit]
|
||||
StopWhenUnneeded=true
|
||||
Wants=<%= process_names.join(' ') %>
|
||||
12
data/export/upstart/master.conf.erb
Normal file
12
data/export/upstart/master.conf.erb
Normal file
@@ -0,0 +1,12 @@
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p <%= log %>
|
||||
chown -R <%= user %> <%= log %>
|
||||
EOF
|
||||
|
||||
end script
|
||||
|
||||
start on runlevel [2345]
|
||||
|
||||
stop on runlevel [016]
|
||||
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 %>-<%= name %>
|
||||
stop on stopping <%= app %>-<%= name %>
|
||||
respawn
|
||||
|
||||
exec su - <%= user %> -s /bin/sh -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> exec <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'
|
||||
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 %>
|
||||
33
dist/deb.rake
vendored
Normal file
33
dist/deb.rake
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
file pkg("/apt-#{version}/foreman-#{version}.deb") => distribution_files("deb") do |t|
|
||||
mkchdir(File.dirname(t.name)) do
|
||||
mkchdir("usr/local/foreman") do
|
||||
assemble_distribution
|
||||
assemble_gems
|
||||
assemble resource("deb/foreman"), "bin/foreman", 0755
|
||||
File.chmod 0755, "bin/foreman-runner"
|
||||
end
|
||||
|
||||
assemble resource("deb/control"), "control"
|
||||
assemble resource("deb/postinst"), "postinst"
|
||||
|
||||
sh "tar czvf data.tar.gz usr/local/foreman --owner=root --group=root"
|
||||
sh "tar czvf control.tar.gz control postinst"
|
||||
|
||||
File.open("debian-binary", "w") do |f|
|
||||
f.puts "2.0"
|
||||
end
|
||||
|
||||
deb = File.basename(t.name)
|
||||
|
||||
sh "ar -r #{t.name} debian-binary control.tar.gz data.tar.gz"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Build a .deb package"
|
||||
task "deb:build" => pkg("/apt-#{version}/foreman-#{version}.deb")
|
||||
|
||||
desc "Remove build artifacts for .deb"
|
||||
task "deb:clean" do
|
||||
clean pkg("foreman-#{version}.deb")
|
||||
FileUtils.rm_rf("pkg/apt-#{version}") if Dir.exists?("pkg/apt-#{version}")
|
||||
end
|
||||
14
dist/gem.rake
vendored
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 "gem push #{pkg("foreman-#{version}.gem")} || echo 'error'"
|
||||
end
|
||||
16
dist/jruby.rake
vendored
Normal file
16
dist/jruby.rake
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
file pkg("foreman-#{version}-jruby.gem") => distribution_files do |t|
|
||||
Bundler.with_clean_env do
|
||||
sh "env PLATFORM=java gem build foreman.gemspec"
|
||||
end
|
||||
sh "mv foreman-#{version}-java.gem #{t.name}"
|
||||
end
|
||||
|
||||
task "jruby:build" => pkg("foreman-#{version}-jruby.gem")
|
||||
|
||||
task "jruby:clean" do
|
||||
clean pkg("foreman-#{version}-jruby.gem")
|
||||
end
|
||||
|
||||
task "jruby:release" => "jruby:build" do |t|
|
||||
sh "gem push #{pkg("foreman-#{version}-jruby.gem")} || echo 'error'"
|
||||
end
|
||||
16
dist/mingw32.rake
vendored
Normal file
16
dist/mingw32.rake
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
file pkg("foreman-#{version}-mingw32.gem") => distribution_files do |t|
|
||||
Bundler.with_clean_env do
|
||||
sh "env PLATFORM=mingw32 gem build foreman.gemspec"
|
||||
end
|
||||
sh "mv foreman-#{version}-mingw32.gem #{t.name}"
|
||||
end
|
||||
|
||||
task "mingw32:build" => pkg("foreman-#{version}-mingw32.gem")
|
||||
|
||||
task "mingw32:clean" do
|
||||
clean pkg("foreman-#{version}-mingw32.gem")
|
||||
end
|
||||
|
||||
task "mingw32:release" => "mingw32:build" do |t|
|
||||
sh "gem push #{pkg("foreman-#{version}-mingw32.gem")} || echo 'error'"
|
||||
end
|
||||
53
dist/pkg.rake
vendored
Normal file
53
dist/pkg.rake
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
require "erb"
|
||||
|
||||
file pkg("foreman-#{version}.pkg") => distribution_files do |t|
|
||||
tempdir do |dir|
|
||||
mkchdir("foreman") do
|
||||
assemble_distribution
|
||||
assemble_gems
|
||||
assemble resource("pkg/foreman"), "bin/foreman", 0755
|
||||
end
|
||||
|
||||
kbytes = %x{ du -ks foreman | cut -f 1 }
|
||||
num_files = %x{ find foreman | wc -l }
|
||||
|
||||
mkdir_p "pkg"
|
||||
mkdir_p "pkg/Resources"
|
||||
mkdir_p "pkg/foreman.pkg"
|
||||
|
||||
dist = File.read(resource("pkg/Distribution.erb"))
|
||||
dist = ERB.new(dist).result(binding)
|
||||
File.open("pkg/Distribution", "w") { |f| f.puts dist }
|
||||
|
||||
dist = File.read(resource("pkg/PackageInfo.erb"))
|
||||
dist = ERB.new(dist).result(binding)
|
||||
File.open("pkg/foreman.pkg/PackageInfo", "w") { |f| f.puts dist }
|
||||
|
||||
mkdir_p "pkg/foreman.pkg/Scripts"
|
||||
cp resource("pkg/postinstall"), "pkg/foreman.pkg/Scripts/postinstall"
|
||||
chmod 0755, "pkg/foreman.pkg/Scripts/postinstall"
|
||||
|
||||
sh %{ mkbom -s foreman pkg/foreman.pkg/Bom }
|
||||
|
||||
Dir.chdir("foreman") do
|
||||
sh %{ pax -wz -x cpio . > ../pkg/foreman.pkg/Payload }
|
||||
end
|
||||
|
||||
sh %{ pkgutil --flatten pkg foreman-#{version}.pkg }
|
||||
|
||||
FileUtils.mkdir_p(File.dirname(t.name))
|
||||
cp_r "foreman-#{version}.pkg", t.name
|
||||
end
|
||||
end
|
||||
|
||||
task "pkg:build" => pkg("foreman-#{version}.pkg")
|
||||
|
||||
task "pkg:clean" do
|
||||
clean pkg("foreman-#{version}.pkg")
|
||||
end
|
||||
|
||||
task "pkg:release" => "pkg:build" do |t|
|
||||
store pkg("foreman-#{version}.pkg"), "foreman/foreman-#{version}.pkg"
|
||||
store pkg("foreman-#{version}.pkg"), "foreman/foreman-beta.pkg" if beta?
|
||||
store pkg("foreman-#{version}.pkg"), "foreman/foreman.pkg" unless beta?
|
||||
end
|
||||
12
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"/>
|
||||
</choices-outline>
|
||||
<choice id="git" title="git" start_selected="false" start_enabled="false" selected="needs_git()" enabled="needs_git()">
|
||||
<pkg-ref id="git.pkg" />
|
||||
</choice>
|
||||
<choice id="foreman" title="foreman">
|
||||
<pkg-ref id="io.foreman.installer"/>
|
||||
</choice>
|
||||
<pkg-ref id="io.foreman.installer" installKBytes="<%= kbytes %>" version="<%= version %>" auth="Root">#foreman.pkg</pkg-ref>
|
||||
</installer-script>
|
||||
|
||||
6
dist/resources/pkg/PackageInfo.erb
vendored
Normal file
6
dist/resources/pkg/PackageInfo.erb
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<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_f "bin/foreman"
|
||||
assemble resource("tgz/foreman"), "bin/foreman", 0755
|
||||
end
|
||||
|
||||
sh "tar czvf #{t.name} foreman"
|
||||
end
|
||||
end
|
||||
|
||||
task "tgz:build" => pkg("foreman-#{version}.tgz")
|
||||
|
||||
task "tgz:clean" do
|
||||
clean pkg("foreman-#{version}.tgz")
|
||||
end
|
||||
|
||||
task "tgz:release" => "tgz:build" do |t|
|
||||
store pkg("foreman-#{version}.tgz"), "foreman/foreman-#{version}.tgz"
|
||||
store pkg("foreman-#{version}.tgz"), "foreman/foreman-beta.tgz" if beta?
|
||||
store pkg("foreman-#{version}.tgz"), "foreman/foreman.tgz" unless beta?
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
ticker ./ticker
|
||||
error ./error
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
while true
|
||||
puts "tick"
|
||||
sleep 1
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p /var/log/<%= app %>
|
||||
|
||||
<% engine.processes.keys.sort.each do |process| %>
|
||||
<% 1.upto(concurrency[process]).each do |num| %>
|
||||
start <%=app%>-<%=process%>-<%=num%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
EOF
|
||||
|
||||
end script
|
||||
@@ -1,5 +0,0 @@
|
||||
stop on stopping <%= app %>
|
||||
respawn
|
||||
|
||||
chdir <%= engine.directory %>
|
||||
exec <%= process.command %> >> /var/log/<%=app%>/<%=process.name%>-<%=num%>.log 2>&1
|
||||
107
foreman.gemspec
107
foreman.gemspec
@@ -1,88 +1,31 @@
|
||||
# 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.4.1"
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = "foreman"
|
||||
gem.license = "MIT"
|
||||
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-06-23}
|
||||
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/base.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<term-ansicolor>, ["~> 1.0.5"])
|
||||
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<term-ansicolor>, ["~> 1.0.5"])
|
||||
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<term-ansicolor>, ["~> 1.0.5"])
|
||||
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 'thor', '>= 0.13.6'
|
||||
gem.add_dependency 'dotenv', '>= 0.7'
|
||||
|
||||
if ENV["PLATFORM"] == "java"
|
||||
gem.platform = Gem::Platform.new("java")
|
||||
end
|
||||
|
||||
if ENV["PLATFORM"] == "mingw32"
|
||||
gem.add_dependency "win32console", "~> 1.3.0"
|
||||
gem.platform = Gem::Platform.new("mingw32")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
module Foreman
|
||||
require "foreman/version"
|
||||
|
||||
VERSION = "0.4.1"
|
||||
module Foreman
|
||||
|
||||
class AppDoesNotExist < Exception; end
|
||||
|
||||
end
|
||||
def self.runner
|
||||
File.expand_path("../../bin/foreman-runner", __FILE__)
|
||||
end
|
||||
|
||||
def self.jruby_18?
|
||||
defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java" and ruby_18?
|
||||
end
|
||||
|
||||
def self.ruby_18?
|
||||
defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
|
||||
end
|
||||
|
||||
def self.windows?
|
||||
defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,63 +1,116 @@
|
||||
require "foreman"
|
||||
require "foreman/configuration"
|
||||
require "foreman/helpers"
|
||||
require "foreman/engine"
|
||||
require "foreman/engine/cli"
|
||||
require "foreman/export"
|
||||
require "foreman/version"
|
||||
require "shellwords"
|
||||
require "yaml"
|
||||
require "thor"
|
||||
|
||||
class Foreman::CLI < Thor
|
||||
|
||||
class_option :procfile, :type => :string, :aliases => "-p", :desc => "Default: ./Procfile"
|
||||
include Foreman::Helpers
|
||||
|
||||
desc "start [PROCESS]", "Start the application, or a specific process"
|
||||
map ["-v", "--version"] => :version
|
||||
|
||||
method_option :screen, :type => :boolean, :aliases => "-s"
|
||||
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
||||
class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
|
||||
|
||||
desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
|
||||
|
||||
method_option :color, :type => :boolean, :aliases => "-c", :desc => "Force color to be enabled"
|
||||
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
||||
method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"'
|
||||
method_option :port, :type => :numeric, :aliases => "-p"
|
||||
method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5."
|
||||
|
||||
class << self
|
||||
# Hackery. Take the run method away from Thor so that we can redefine it.
|
||||
def is_thor_reserved_word?(word, type)
|
||||
return false if word == "run"
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def start(process=nil)
|
||||
require_posix_spawn_for_ruby_18!
|
||||
check_procfile!
|
||||
|
||||
if process
|
||||
engine.execute(process)
|
||||
elsif options[:screen]
|
||||
engine.screen
|
||||
else
|
||||
engine.start
|
||||
end
|
||||
load_environment!
|
||||
engine.load_procfile(procfile)
|
||||
engine.options[:formation] = "#{process}=1" if process
|
||||
engine.start
|
||||
end
|
||||
|
||||
desc "export FORMAT LOCATION", "Export the application to another process management format"
|
||||
|
||||
method_option :app, :type => :string, :aliases => "-a"
|
||||
method_option :concurrency, :type => :string, :aliases => "-c",
|
||||
:banner => '"alpha=5,bar=3"'
|
||||
method_option :app, :type => :string, :aliases => "-a"
|
||||
method_option :log, :type => :string, :aliases => "-l"
|
||||
method_option :run, :type => :string, :aliases => "-r", :desc => "Specify the pid file directory, defaults to /var/run/<application>"
|
||||
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
||||
method_option :port, :type => :numeric, :aliases => "-p"
|
||||
method_option :user, :type => :string, :aliases => "-u"
|
||||
method_option :template, :type => :string, :aliases => "-t"
|
||||
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
||||
|
||||
def export(format, location=nil)
|
||||
check_procfile!
|
||||
|
||||
formatter = case format
|
||||
when "upstart" then Foreman::Export::Upstart
|
||||
else error "Unknown export format: #{format}."
|
||||
end
|
||||
|
||||
formatter.new(engine).export(location,
|
||||
:name => options[:app],
|
||||
:concurrency => options[:concurrency]
|
||||
)
|
||||
load_environment!
|
||||
engine.load_procfile(procfile)
|
||||
formatter = Foreman::Export.formatter(format)
|
||||
formatter.new(location, engine, options).export
|
||||
rescue Foreman::Export::Exception => ex
|
||||
error ex.message
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
desc "check", "Validate your application's Procfile"
|
||||
|
||||
def check_procfile!
|
||||
error("Procfile does not exist.") unless File.exist?(procfile)
|
||||
def check
|
||||
check_procfile!
|
||||
engine.load_procfile(procfile)
|
||||
error "no processes defined" unless engine.processes.length > 0
|
||||
puts "valid procfile detected (#{engine.process_names.join(', ')})"
|
||||
end
|
||||
|
||||
def engine
|
||||
@engine ||= Foreman::Engine.new(procfile)
|
||||
desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
|
||||
|
||||
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
||||
|
||||
def run(*args)
|
||||
load_environment!
|
||||
|
||||
if File.exist?(procfile)
|
||||
engine.load_procfile(procfile)
|
||||
end
|
||||
|
||||
begin
|
||||
engine.env.each { |k,v| ENV[k] = v }
|
||||
if args.size == 1 && process = engine.process(args.first)
|
||||
process.exec(:env => engine.env)
|
||||
else
|
||||
exec args.shelljoin
|
||||
end
|
||||
rescue Errno::EACCES
|
||||
error "not executable: #{args.first}"
|
||||
rescue Errno::ENOENT
|
||||
error "command not found: #{args.first}"
|
||||
end
|
||||
end
|
||||
|
||||
def procfile
|
||||
options[:procfile] || "./Procfile"
|
||||
desc "version", "Display Foreman gem version"
|
||||
|
||||
def version
|
||||
puts Foreman::VERSION
|
||||
end
|
||||
|
||||
no_tasks do
|
||||
def engine
|
||||
@engine ||= begin
|
||||
engine_class = Foreman::Engine::CLI
|
||||
engine = engine_class.new(options)
|
||||
engine
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
@@ -67,8 +120,42 @@ private ######################################################################
|
||||
exit 1
|
||||
end
|
||||
|
||||
def procfile_exists?(procfile)
|
||||
File.exist?(procfile)
|
||||
def check_procfile!
|
||||
error("#{procfile} does not exist.") unless File.exist?(procfile)
|
||||
end
|
||||
|
||||
def load_environment!
|
||||
if options[:env]
|
||||
options[:env].split(",").each do |file|
|
||||
engine.load_env file
|
||||
end
|
||||
else
|
||||
default_env = File.join(engine.root, ".env")
|
||||
engine.load_env default_env if File.exists?(default_env)
|
||||
end
|
||||
end
|
||||
|
||||
def require_posix_spawn_for_ruby_18!
|
||||
begin
|
||||
Kernel.require 'posix/spawn' # Use Kernel explicitly so we can mock the require call in the spec
|
||||
rescue LoadError
|
||||
error "foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`."
|
||||
end if Foreman.ruby_18?
|
||||
end
|
||||
|
||||
def procfile
|
||||
case
|
||||
when options[:procfile] then options[:procfile]
|
||||
when options[:root] then File.expand_path(File.join(options[:root], "Procfile"))
|
||||
else "Procfile"
|
||||
end
|
||||
end
|
||||
|
||||
def options
|
||||
original_options = super
|
||||
return original_options unless File.exists?(".foreman")
|
||||
defaults = ::YAML::load_file(".foreman") || {}
|
||||
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
||||
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,154 +1,421 @@
|
||||
require "foreman"
|
||||
require "foreman/process"
|
||||
require "pty"
|
||||
require "foreman/procfile"
|
||||
require "dotenv"
|
||||
require "tempfile"
|
||||
require "term/ansicolor"
|
||||
require "timeout"
|
||||
require "fileutils"
|
||||
require "thread"
|
||||
|
||||
class Foreman::Engine
|
||||
|
||||
attr_reader :procfile
|
||||
attr_reader :directory
|
||||
# The signals that the engine cares about.
|
||||
#
|
||||
HANDLED_SIGNALS = [ :TERM, :INT, :HUP ]
|
||||
|
||||
extend Term::ANSIColor
|
||||
attr_reader :env
|
||||
attr_reader :options
|
||||
attr_reader :processes
|
||||
|
||||
COLORS = [ cyan, yellow, green, magenta, on_blue ]
|
||||
# Create an +Engine+ for running processes
|
||||
#
|
||||
# @param [Hash] options
|
||||
#
|
||||
# @option options [String] :formation (all=1) The process formation to use
|
||||
# @option options [Fixnum] :port (5000) The base port to assign to processes
|
||||
# @option options [String] :root (Dir.pwd) The root directory from which to run processes
|
||||
#
|
||||
def initialize(options={})
|
||||
@options = options.dup
|
||||
|
||||
def initialize(procfile)
|
||||
@procfile = read_procfile(procfile)
|
||||
@directory = File.expand_path(File.dirname(procfile))
|
||||
@options[:formation] ||= (options[:concurrency] || "all=1")
|
||||
@options[:timeout] ||= 5
|
||||
|
||||
@env = {}
|
||||
@mutex = Mutex.new
|
||||
@names = {}
|
||||
@processes = []
|
||||
@running = {}
|
||||
@readers = {}
|
||||
|
||||
# Self-pipe for deferred signal-handling (ala djb: http://cr.yp.to/docs/selfpipe.html)
|
||||
reader, writer = create_pipe
|
||||
reader.close_on_exec = true if reader.respond_to?(:close_on_exec)
|
||||
writer.close_on_exec = true if writer.respond_to?(:close_on_exec)
|
||||
@selfpipe = { :reader => reader, :writer => writer }
|
||||
|
||||
# Set up a global signal queue
|
||||
# http://blog.rubybestpractices.com/posts/ewong/016-Implementing-Signal-Handlers.html
|
||||
Thread.main[:signal_queue] = []
|
||||
end
|
||||
|
||||
def processes
|
||||
@processes ||= begin
|
||||
procfile.split("\n").inject({}) do |hash, line|
|
||||
next if line.strip == ""
|
||||
process = Foreman::Process.new(*line.split(" ", 2))
|
||||
process.color = next_color
|
||||
hash.update(process.name => process)
|
||||
# Start the processes registered to this +Engine+
|
||||
#
|
||||
def start
|
||||
register_signal_handlers
|
||||
startup
|
||||
spawn_processes
|
||||
watch_for_output
|
||||
sleep 0.1
|
||||
watch_for_termination { terminate_gracefully }
|
||||
shutdown
|
||||
end
|
||||
|
||||
# Set up deferred signal handlers
|
||||
#
|
||||
def register_signal_handlers
|
||||
HANDLED_SIGNALS.each do |sig|
|
||||
if ::Signal.list.include? sig.to_s
|
||||
trap(sig) { Thread.main[:signal_queue] << sig ; notice_signal }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
proctitle "ruby: foreman master"
|
||||
|
||||
processes.each do |name, process|
|
||||
fork process
|
||||
# Unregister deferred signal handlers
|
||||
#
|
||||
def restore_default_signal_handlers
|
||||
HANDLED_SIGNALS.each do |sig|
|
||||
trap(sig, :DEFAULT) if ::Signal.list.include? sig.to_s
|
||||
end
|
||||
|
||||
trap("TERM") { kill_and_exit("TERM") }
|
||||
trap("INT") { kill_and_exit("INT") }
|
||||
|
||||
watch_for_termination
|
||||
end
|
||||
|
||||
def screen
|
||||
tempfile = Tempfile.new("foreman")
|
||||
tempfile.puts "sessionname foreman"
|
||||
processes.each do |name, process|
|
||||
tempfile.puts "screen -t #{name} #{process.command}"
|
||||
# Wake the main thread up via the selfpipe when there's a signal
|
||||
#
|
||||
def notice_signal
|
||||
@selfpipe[:writer].write_nonblock( '.' )
|
||||
rescue Errno::EAGAIN
|
||||
# Ignore writes that would block
|
||||
rescue Errno::EINT
|
||||
# Retry if another signal arrived while writing
|
||||
retry
|
||||
end
|
||||
|
||||
# Invoke the real handler for signal +sig+. This shouldn't be called directly
|
||||
# by signal handlers, as it might invoke code which isn't re-entrant.
|
||||
#
|
||||
# @param [Symbol] sig the name of the signal to be handled
|
||||
#
|
||||
def handle_signal(sig)
|
||||
case sig
|
||||
when :TERM
|
||||
handle_term_signal
|
||||
when :INT
|
||||
handle_interrupt
|
||||
when :HUP
|
||||
handle_hangup
|
||||
else
|
||||
system "unhandled signal #{sig}"
|
||||
end
|
||||
tempfile.close
|
||||
|
||||
system "screen -c #{tempfile.path}"
|
||||
|
||||
tempfile.delete
|
||||
end
|
||||
|
||||
def execute(name)
|
||||
run(processes[name], false)
|
||||
# Handle a TERM signal
|
||||
#
|
||||
def handle_term_signal
|
||||
puts "SIGTERM received"
|
||||
terminate_gracefully
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
# Handle an INT signal
|
||||
#
|
||||
def handle_interrupt
|
||||
puts "SIGINT received"
|
||||
terminate_gracefully
|
||||
end
|
||||
|
||||
def fork(process)
|
||||
pid = Process.fork do
|
||||
run(process)
|
||||
# Handle a HUP signal
|
||||
#
|
||||
def handle_hangup
|
||||
puts "SIGHUP received"
|
||||
terminate_gracefully
|
||||
end
|
||||
|
||||
# Register a process to be run by this +Engine+
|
||||
#
|
||||
# @param [String] name A name for this process
|
||||
# @param [String] command The command to run
|
||||
# @param [Hash] options
|
||||
#
|
||||
# @option options [Hash] :env A custom environment for this process
|
||||
#
|
||||
def register(name, command, options={})
|
||||
options[:env] ||= env
|
||||
options[:cwd] ||= File.dirname(command.split(" ").first)
|
||||
process = Foreman::Process.new(command, options)
|
||||
@names[process] = name
|
||||
@processes << process
|
||||
end
|
||||
|
||||
# Clear the processes registered to this +Engine+
|
||||
#
|
||||
def clear
|
||||
@names = {}
|
||||
@processes = []
|
||||
end
|
||||
|
||||
# Register processes by reading a Procfile
|
||||
#
|
||||
# @param [String] filename A Procfile from which to read processes to register
|
||||
#
|
||||
def load_procfile(filename)
|
||||
options[:root] ||= File.dirname(filename)
|
||||
Foreman::Procfile.new(filename).entries do |name, command|
|
||||
register name, command, :cwd => options[:root]
|
||||
end
|
||||
|
||||
info "started with pid #{pid}", process
|
||||
running_processes[pid] = process
|
||||
self
|
||||
end
|
||||
|
||||
def run(process, log_to_file=true)
|
||||
proctitle "ruby: foreman #{process.name}"
|
||||
|
||||
Dir.chdir directory do
|
||||
FileUtils.mkdir_p "log"
|
||||
command = process.command
|
||||
# Load a .env file into the +env+ for this +Engine+
|
||||
#
|
||||
# @param [String] filename A .env file to load into the environment
|
||||
#
|
||||
def load_env(filename)
|
||||
@env.update Dotenv::Environment.new(filename)
|
||||
end
|
||||
|
||||
# Send a signal to all processes started by this +Engine+
|
||||
#
|
||||
# @param [String] signal The signal to send to each process
|
||||
#
|
||||
def kill_children( signal="SIGTERM" )
|
||||
@running.each do |pid, (process, index)|
|
||||
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
|
||||
begin
|
||||
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
|
||||
until stdin.eof?
|
||||
info stdin.gets, process
|
||||
Process.kill( signal, pid )
|
||||
rescue Errno::ESRCH, Errno::EPERM => err
|
||||
system " %p when sending signal %p to pid %d: %s" %
|
||||
[ err.class, signal, pid, err.message ]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Send a signal to the whole process group.
|
||||
#
|
||||
# @param [String] signal The signal to send
|
||||
#
|
||||
def killall(signal="SIGTERM")
|
||||
kill_children(signal)
|
||||
end
|
||||
|
||||
# Get the process formation
|
||||
#
|
||||
# @returns [Fixnum] The formation count for the specified process
|
||||
#
|
||||
def formation
|
||||
@formation ||= parse_formation(options[:formation])
|
||||
end
|
||||
|
||||
# List the available process names
|
||||
#
|
||||
# @returns [Array] A list of process names
|
||||
#
|
||||
def process_names
|
||||
@processes.map { |p| @names[p] }
|
||||
end
|
||||
|
||||
# Get the +Process+ for a specifid name
|
||||
#
|
||||
# @param [String] name The process name
|
||||
#
|
||||
# @returns [Foreman::Process] The +Process+ for the specified name
|
||||
#
|
||||
def process(name)
|
||||
@names.invert[name]
|
||||
end
|
||||
|
||||
# Yield each +Process+ in order
|
||||
#
|
||||
def each_process
|
||||
process_names.each do |name|
|
||||
yield name, process(name)
|
||||
end
|
||||
end
|
||||
|
||||
# Get the root directory for this +Engine+
|
||||
#
|
||||
# @returns [String] The root directory
|
||||
#
|
||||
def root
|
||||
File.expand_path(options[:root] || Dir.pwd)
|
||||
end
|
||||
|
||||
# Get the port for a given process and offset
|
||||
#
|
||||
# @param [Foreman::Process] process A +Process+ associated with this engine
|
||||
# @param [Fixnum] instance The instance of the process
|
||||
#
|
||||
# @returns [Fixnum] port The port to use for this instance of this process
|
||||
#
|
||||
def port_for(process, instance, base=nil)
|
||||
if base
|
||||
base + (@processes.index(process.process) * 100) + (instance - 1)
|
||||
else
|
||||
base_port + (@processes.index(process) * 100) + (instance - 1)
|
||||
end
|
||||
end
|
||||
|
||||
# Get the base port for this foreman instance
|
||||
#
|
||||
# @returns [Fixnum] port The base port
|
||||
#
|
||||
def base_port
|
||||
(options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
|
||||
end
|
||||
|
||||
# deprecated
|
||||
def environment
|
||||
env
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
### Engine API ######################################################
|
||||
|
||||
def startup
|
||||
raise TypeError, "must use a subclass of Foreman::Engine"
|
||||
end
|
||||
|
||||
def output(name, data)
|
||||
raise TypeError, "must use a subclass of Foreman::Engine"
|
||||
end
|
||||
|
||||
def shutdown
|
||||
raise TypeError, "must use a subclass of Foreman::Engine"
|
||||
end
|
||||
|
||||
## Helpers ##########################################################
|
||||
|
||||
def create_pipe
|
||||
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
|
||||
end
|
||||
|
||||
def name_for(pid)
|
||||
process, index = @running[pid]
|
||||
name_for_index(process, index)
|
||||
end
|
||||
|
||||
def name_for_index(process, index)
|
||||
[ @names[process], index.to_s ].compact.join(".")
|
||||
end
|
||||
|
||||
def parse_formation(formation)
|
||||
pairs = formation.to_s.gsub(/\s/, "").split(",")
|
||||
|
||||
pairs.inject(Hash.new(0)) do |ax, pair|
|
||||
process, amount = pair.split("=")
|
||||
process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
|
||||
ax
|
||||
end
|
||||
end
|
||||
|
||||
def output_with_mutex(name, message)
|
||||
@mutex.synchronize do
|
||||
output name, message
|
||||
end
|
||||
end
|
||||
|
||||
def system(message)
|
||||
output_with_mutex "system", message
|
||||
end
|
||||
|
||||
def termination_message_for(status)
|
||||
if status.exited?
|
||||
"exited with code #{status.exitstatus}"
|
||||
elsif status.signaled?
|
||||
"terminated by SIG#{Signal.list.invert[status.termsig]}"
|
||||
else
|
||||
"died a mysterious death"
|
||||
end
|
||||
end
|
||||
|
||||
def flush_reader(reader)
|
||||
until reader.eof?
|
||||
data = reader.gets
|
||||
output_with_mutex name_for(@readers.key(reader)), data
|
||||
end
|
||||
end
|
||||
|
||||
## Engine ###########################################################
|
||||
|
||||
def spawn_processes
|
||||
@processes.each do |process|
|
||||
1.upto(formation[@names[process]]) do |n|
|
||||
reader, writer = create_pipe
|
||||
begin
|
||||
pid = process.run(:output => writer, :env => {
|
||||
"PORT" => port_for(process, n).to_s,
|
||||
"PS" => name_for_index(process, n)
|
||||
})
|
||||
writer.puts "started with pid #{pid}"
|
||||
rescue Errno::ENOENT
|
||||
writer.puts "unknown command: #{process.command}"
|
||||
end
|
||||
@running[pid] = [process, n]
|
||||
@readers[pid] = reader
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def watch_for_output
|
||||
Thread.new do
|
||||
begin
|
||||
loop do
|
||||
io = IO.select([@selfpipe[:reader]] + @readers.values, nil, nil, 30)
|
||||
|
||||
begin
|
||||
@selfpipe[:reader].read_nonblock(11)
|
||||
rescue Errno::EAGAIN, Errno::EINTR => err
|
||||
# ignore
|
||||
end
|
||||
|
||||
# Look for any signals that arrived and handle them
|
||||
while sig = Thread.main[:signal_queue].shift
|
||||
self.handle_signal(sig)
|
||||
end
|
||||
|
||||
(io.nil? ? [] : io.first).each do |reader|
|
||||
next if reader == @selfpipe[:reader]
|
||||
|
||||
if reader.eof?
|
||||
@readers.delete_if { |key, value| value == reader }
|
||||
else
|
||||
data = reader.gets
|
||||
output_with_mutex name_for(@readers.invert[reader]), data
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue PTY::ChildExited, Interrupt
|
||||
info "process exiting", process
|
||||
rescue Exception => ex
|
||||
puts ex.message
|
||||
puts ex.backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def kill_and_exit(signal="TERM")
|
||||
info "terminating"
|
||||
running_processes.each do |pid, process|
|
||||
info "killing #{process.name} in pid #{pid}"
|
||||
Process.kill(signal, pid)
|
||||
end
|
||||
exit 0
|
||||
end
|
||||
|
||||
def info(message, process=nil)
|
||||
print process.color if process
|
||||
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
|
||||
print Term::ANSIColor.reset
|
||||
print message.chomp
|
||||
puts
|
||||
end
|
||||
|
||||
def longest_process_name
|
||||
@longest_process_name ||= begin
|
||||
longest = processes.keys.map { |name| name.length }.sort.last
|
||||
longest = 6 if longest < 6 # system
|
||||
longest
|
||||
end
|
||||
end
|
||||
|
||||
def pad_process_name(process)
|
||||
name = process ? process.name : "system"
|
||||
name.ljust(longest_process_name)
|
||||
end
|
||||
|
||||
def print_info
|
||||
info "currently running processes:"
|
||||
running_processes.each do |pid, process|
|
||||
info "pid #{pid}", process
|
||||
end
|
||||
end
|
||||
|
||||
def proctitle(title)
|
||||
$0 = title
|
||||
end
|
||||
|
||||
def read_procfile(procfile)
|
||||
File.read(procfile)
|
||||
end
|
||||
|
||||
def watch_for_termination
|
||||
pid, status = Process.wait2
|
||||
process = running_processes.delete(pid)
|
||||
info "process terminated", process
|
||||
kill_and_exit
|
||||
output_with_mutex name_for(pid), termination_message_for(status)
|
||||
@running.delete(pid)
|
||||
yield if block_given?
|
||||
pid
|
||||
rescue Errno::ECHILD
|
||||
end
|
||||
|
||||
def running_processes
|
||||
@running_processes ||= {}
|
||||
end
|
||||
|
||||
def next_color
|
||||
@current_color ||= -1
|
||||
@current_color += 1
|
||||
@current_color >= COLORS.length ? "" : COLORS[@current_color]
|
||||
def terminate_gracefully
|
||||
return if @terminating
|
||||
restore_default_signal_handlers
|
||||
@terminating = true
|
||||
if Foreman.windows?
|
||||
system "sending SIGKILL to all processes"
|
||||
kill_children "SIGKILL"
|
||||
else
|
||||
system "sending SIGTERM to all processes"
|
||||
kill_children "SIGTERM"
|
||||
end
|
||||
Timeout.timeout(options[:timeout]) do
|
||||
watch_for_termination while @running.length > 0
|
||||
end
|
||||
rescue Timeout::Error
|
||||
system "sending SIGKILL to all processes"
|
||||
kill_children "SIGKILL"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
104
lib/foreman/engine/cli.rb
Normal file
104
lib/foreman/engine/cli.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
require "foreman/engine"
|
||||
|
||||
class Foreman::Engine::CLI < Foreman::Engine
|
||||
|
||||
module Color
|
||||
|
||||
ANSI = {
|
||||
:reset => 0,
|
||||
:black => 30,
|
||||
:red => 31,
|
||||
:green => 32,
|
||||
:yellow => 33,
|
||||
:blue => 34,
|
||||
:magenta => 35,
|
||||
:cyan => 36,
|
||||
:white => 37,
|
||||
:bright_black => 30,
|
||||
:bright_red => 31,
|
||||
:bright_green => 32,
|
||||
:bright_yellow => 33,
|
||||
:bright_blue => 34,
|
||||
:bright_magenta => 35,
|
||||
:bright_cyan => 36,
|
||||
:bright_white => 37,
|
||||
}
|
||||
|
||||
def self.enable(io, force=false)
|
||||
io.extend(self)
|
||||
@@color_force = force
|
||||
end
|
||||
|
||||
def color?
|
||||
return true if @@color_force
|
||||
return false if Foreman.windows?
|
||||
return false unless self.respond_to?(:isatty)
|
||||
self.isatty && ENV["TERM"]
|
||||
end
|
||||
|
||||
def color(name)
|
||||
return "" unless color?
|
||||
return "" unless ansi = ANSI[name.to_sym]
|
||||
"\e[#{ansi}m"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
FOREMAN_COLORS = %w( cyan yellow green magenta red blue bright_cyan bright_yellow
|
||||
bright_green bright_magenta bright_red bright_blue )
|
||||
|
||||
def startup
|
||||
@colors = map_colors
|
||||
proctitle "foreman: master" unless Foreman.windows?
|
||||
Color.enable($stdout, options[:color])
|
||||
end
|
||||
|
||||
def output(name, data)
|
||||
data.to_s.lines.map(&:chomp).each do |message|
|
||||
output = ""
|
||||
output += $stdout.color(@colors[name.split(".").first].to_sym)
|
||||
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
||||
output += $stdout.color(:reset)
|
||||
output += message
|
||||
$stdout.puts output
|
||||
$stdout.flush
|
||||
end
|
||||
rescue Errno::EPIPE
|
||||
terminate_gracefully
|
||||
end
|
||||
|
||||
def shutdown
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def name_padding
|
||||
@name_padding ||= begin
|
||||
index_padding = @names.values.map { |n| formation[n] }.max.to_s.length + 1
|
||||
name_padding = @names.values.map { |n| n.length + index_padding }.sort.last
|
||||
[ 6, name_padding ].max
|
||||
end
|
||||
end
|
||||
|
||||
def pad_process_name(name)
|
||||
name.ljust(name_padding, " ")
|
||||
end
|
||||
|
||||
def map_colors
|
||||
colors = Hash.new("white")
|
||||
@names.values.each_with_index do |name, index|
|
||||
colors[name] = FOREMAN_COLORS[index % FOREMAN_COLORS.length]
|
||||
end
|
||||
colors["system"] = "bright_white"
|
||||
colors
|
||||
end
|
||||
|
||||
def proctitle(title)
|
||||
$0 = title
|
||||
end
|
||||
|
||||
def termtitle(title)
|
||||
printf("\033]0;#{title}\007") unless Foreman.windows?
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,7 +1,36 @@
|
||||
require "foreman"
|
||||
require "foreman/helpers"
|
||||
require "pathname"
|
||||
|
||||
module Foreman::Export
|
||||
extend Foreman::Helpers
|
||||
|
||||
class Exception < ::Exception; end
|
||||
|
||||
def self.formatter(format)
|
||||
begin
|
||||
require "foreman/export/#{ format.tr('-', '_') }"
|
||||
classy_format = classify(format)
|
||||
formatter = constantize("Foreman::Export::#{ classy_format }")
|
||||
rescue NameError => ex
|
||||
error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })."
|
||||
rescue LoadError => ex
|
||||
error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')."
|
||||
end
|
||||
end
|
||||
|
||||
def self.error(message)
|
||||
raise Foreman::Export::Exception.new(message)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
require "foreman/export/base"
|
||||
require "foreman/export/inittab"
|
||||
require "foreman/export/upstart"
|
||||
require "foreman/export/daemon"
|
||||
require "foreman/export/bluepill"
|
||||
require "foreman/export/runit"
|
||||
require "foreman/export/supervisord"
|
||||
require "foreman/export/launchd"
|
||||
require "foreman/export/systemd"
|
||||
|
||||
@@ -1,20 +1,87 @@
|
||||
require "foreman/configuration"
|
||||
require "foreman/export"
|
||||
require "ostruct"
|
||||
require "pathname"
|
||||
require "shellwords"
|
||||
|
||||
class Foreman::Export::Base
|
||||
|
||||
attr_reader :location
|
||||
attr_reader :engine
|
||||
attr_reader :options
|
||||
attr_reader :formation
|
||||
|
||||
def initialize(engine)
|
||||
@engine = engine
|
||||
# deprecated
|
||||
attr_reader :port
|
||||
|
||||
def initialize(location, engine, options={})
|
||||
@location = location
|
||||
@engine = engine
|
||||
@options = options.dup
|
||||
@formation = engine.formation
|
||||
|
||||
# deprecated
|
||||
def port
|
||||
Foreman::Export::Base.warn_deprecation!
|
||||
engine.base_port
|
||||
end
|
||||
|
||||
# deprecated
|
||||
def template
|
||||
Foreman::Export::Base.warn_deprecation!
|
||||
options[:template]
|
||||
end
|
||||
|
||||
# deprecated
|
||||
def @engine.procfile
|
||||
Foreman::Export::Base.warn_deprecation!
|
||||
@processes.map do |process|
|
||||
OpenStruct.new(
|
||||
:name => @names[process],
|
||||
:process => process
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def export
|
||||
raise "export method must be overridden"
|
||||
error("Must specify a location") unless location
|
||||
FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
|
||||
FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
|
||||
FileUtils.mkdir_p(run) rescue error("Could not create: #{run}")
|
||||
FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
|
||||
FileUtils.chown(user, nil, run) rescue error("Could not chown #{run} to #{user}")
|
||||
end
|
||||
|
||||
def app
|
||||
options[:app] || "app"
|
||||
end
|
||||
|
||||
def log
|
||||
options[:log] || "/var/log/#{app}"
|
||||
end
|
||||
|
||||
def run
|
||||
options[:run] || "/var/run/#{app}"
|
||||
end
|
||||
|
||||
def user
|
||||
options[:user] || app
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def self.warn_deprecation!
|
||||
@@deprecation_warned ||= false
|
||||
return if @@deprecation_warned
|
||||
puts "WARNING: Using deprecated exporter interface. Please update your exporter"
|
||||
puts "the interface shown in the upstart exporter:"
|
||||
puts
|
||||
puts "https://github.com/ddollar/foreman/blob/master/lib/foreman/export/upstart.rb"
|
||||
puts "https://github.com/ddollar/foreman/blob/master/data/export/upstart/process.conf.erb"
|
||||
puts
|
||||
@@deprecation_warned = true
|
||||
end
|
||||
|
||||
def error(message)
|
||||
raise Foreman::Export::Exception.new(message)
|
||||
end
|
||||
@@ -22,24 +89,61 @@ private ######################################################################
|
||||
def say(message)
|
||||
puts "[foreman export] %s" % message
|
||||
end
|
||||
|
||||
def export_template(name)
|
||||
File.read(File.expand_path("../../../../export/#{name}", __FILE__))
|
||||
|
||||
def clean(filename)
|
||||
return unless File.exists?(filename)
|
||||
say "cleaning up: #{filename}"
|
||||
FileUtils.rm(filename)
|
||||
end
|
||||
|
||||
def parse_concurrency(concurrency)
|
||||
@concurrency ||= begin
|
||||
pairs = concurrency.to_s.gsub(/\s/, "").split(",")
|
||||
pairs.inject(Hash.new(1)) do |hash, pair|
|
||||
process, amount = pair.split("=")
|
||||
hash.update(process => amount.to_i)
|
||||
end
|
||||
def shell_quote(value)
|
||||
Shellwords.escape(value)
|
||||
end
|
||||
|
||||
# deprecated
|
||||
def old_export_template(exporter, file, template_root)
|
||||
if template_root && File.exist?(file_path = File.join(template_root, file))
|
||||
File.read(file_path)
|
||||
elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
|
||||
File.read(file_path)
|
||||
else
|
||||
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
|
||||
end
|
||||
end
|
||||
|
||||
def export_template(name, file=nil, template_root=nil)
|
||||
if file && template_root
|
||||
old_export_template name, file, template_root
|
||||
else
|
||||
name_without_first = name.split("/")[1..-1].join("/")
|
||||
matchers = []
|
||||
matchers << File.join(options[:template], name_without_first) if options[:template]
|
||||
matchers << File.expand_path("~/.foreman/templates/#{name}")
|
||||
matchers << File.expand_path("../../../../data/export/#{name}", __FILE__)
|
||||
File.read(matchers.detect { |m| File.exists?(m) })
|
||||
end
|
||||
end
|
||||
|
||||
def write_template(name, target, binding)
|
||||
compiled = ERB.new(export_template(name), nil, '-').result(binding)
|
||||
write_file target, compiled
|
||||
end
|
||||
|
||||
def chmod(mode, file)
|
||||
say "setting #{file} to mode #{mode}"
|
||||
FileUtils.chmod mode, File.join(location, file)
|
||||
end
|
||||
|
||||
def create_directory(dir)
|
||||
say "creating: #{dir}"
|
||||
FileUtils.mkdir_p(File.join(location, dir))
|
||||
end
|
||||
|
||||
def write_file(filename, contents)
|
||||
say "writing: #{filename}"
|
||||
|
||||
filename = File.join(location, filename) unless Pathname.new(filename).absolute?
|
||||
|
||||
File.open(filename, "w") do |file|
|
||||
file.puts contents
|
||||
end
|
||||
|
||||
12
lib/foreman/export/bluepill.rb
Normal file
12
lib/foreman/export/bluepill.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Bluepill < Foreman::Export::Base
|
||||
|
||||
def export
|
||||
super
|
||||
clean "#{location}/#{app}.pill"
|
||||
write_template "bluepill/master.pill.erb", "#{app}.pill", binding
|
||||
end
|
||||
|
||||
end
|
||||
28
lib/foreman/export/daemon.rb
Normal file
28
lib/foreman/export/daemon.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Daemon < Foreman::Export::Base
|
||||
|
||||
def export
|
||||
super
|
||||
|
||||
(Dir["#{location}/#{app}-*.conf"] << "#{location}/#{app}.conf").each do |file|
|
||||
clean file
|
||||
end
|
||||
|
||||
write_template "daemon/master.conf.erb", "#{app}.conf", binding
|
||||
|
||||
engine.each_process do |name, process|
|
||||
next if engine.formation[name] < 1
|
||||
write_template "daemon/process_master.conf.erb", "#{app}-#{name}.conf", binding
|
||||
|
||||
1.upto(engine.formation[name]) do |num|
|
||||
port = engine.port_for(process, num)
|
||||
arguments = process.command.split(" ")
|
||||
executable = arguments.slice!(0)
|
||||
arguments = arguments.size > 0 ? " -- #{arguments.join(' ')}" : ""
|
||||
write_template "daemon/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
42
lib/foreman/export/inittab.rb
Normal file
42
lib/foreman/export/inittab.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Inittab < Foreman::Export::Base
|
||||
|
||||
def export
|
||||
error("Must specify a location") unless location
|
||||
|
||||
inittab = []
|
||||
inittab << "# ----- foreman #{app} processes -----"
|
||||
|
||||
index = 1
|
||||
engine.each_process do |name, process|
|
||||
1.upto(engine.formation[name]) do |num|
|
||||
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
||||
port = engine.port_for(process, num)
|
||||
|
||||
commands = []
|
||||
commands << "cd #{engine.root}"
|
||||
commands << "export PORT=#{port}"
|
||||
engine.env.each_pair do |var, env|
|
||||
commands << "export #{var.upcase}=#{shell_quote(env)}"
|
||||
end
|
||||
commands << "#{process.command} >> #{log}/#{name}-#{num}.log 2>&1"
|
||||
|
||||
inittab << "#{id}:4:respawn:/bin/su - #{user} -c '#{commands.join(";")}'"
|
||||
index += 1
|
||||
end
|
||||
end
|
||||
|
||||
inittab << "# ----- end foreman #{app} processes -----"
|
||||
|
||||
inittab = inittab.join("\n") + "\n"
|
||||
|
||||
if location == "-"
|
||||
puts inittab
|
||||
else
|
||||
say "writing: #{location}"
|
||||
File.open(location, "w") { |file| file.puts inittab }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
17
lib/foreman/export/launchd.rb
Normal file
17
lib/foreman/export/launchd.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Launchd < Foreman::Export::Base
|
||||
|
||||
def export
|
||||
super
|
||||
engine.each_process do |name, process|
|
||||
1.upto(engine.formation[name]) do |num|
|
||||
port = engine.port_for(process, num)
|
||||
command_args = process.command.split(" ")
|
||||
write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
34
lib/foreman/export/runit.rb
Normal file
34
lib/foreman/export/runit.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Runit < Foreman::Export::Base
|
||||
|
||||
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
||||
|
||||
def export
|
||||
super
|
||||
|
||||
engine.each_process do |name, process|
|
||||
1.upto(engine.formation[name]) do |num|
|
||||
process_directory = "#{app}-#{name}-#{num}"
|
||||
|
||||
create_directory process_directory
|
||||
create_directory "#{process_directory}/env"
|
||||
create_directory "#{process_directory}/log"
|
||||
|
||||
write_template "runit/run.erb", "#{process_directory}/run", binding
|
||||
chmod 0755, "#{process_directory}/run"
|
||||
|
||||
port = engine.port_for(process, num)
|
||||
engine.env.merge("PORT" => port.to_s).each do |key, value|
|
||||
write_file "#{process_directory}/env/#{key}", value
|
||||
end
|
||||
|
||||
write_template "runit/log/run.erb", "#{process_directory}/log/run", binding
|
||||
chmod 0755, "#{process_directory}/log/run"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
16
lib/foreman/export/supervisord.rb
Normal file
16
lib/foreman/export/supervisord.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Supervisord < Foreman::Export::Base
|
||||
|
||||
def export
|
||||
super
|
||||
|
||||
Dir["#{location}/#{app}*.conf"].each do |file|
|
||||
clean file
|
||||
end
|
||||
|
||||
write_template "supervisord/app.conf.erb", "#{app}.conf", binding
|
||||
end
|
||||
|
||||
end
|
||||
32
lib/foreman/export/systemd.rb
Normal file
32
lib/foreman/export/systemd.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Systemd < Foreman::Export::Base
|
||||
|
||||
def export
|
||||
super
|
||||
|
||||
Dir["#{location}/#{app}*.target"].concat(Dir["#{location}/#{app}*.service"]).each do |file|
|
||||
clean file
|
||||
end
|
||||
|
||||
process_master_names = []
|
||||
|
||||
engine.each_process do |name, process|
|
||||
next if engine.formation[name] < 1
|
||||
|
||||
process_names = []
|
||||
|
||||
1.upto(engine.formation[name]) do |num|
|
||||
port = engine.port_for(process, num)
|
||||
write_template "systemd/process.service.erb", "#{app}-#{name}-#{num}.service", binding
|
||||
process_names << "#{app}-#{name}-#{num}.service"
|
||||
end
|
||||
|
||||
write_template "systemd/process_master.target.erb", "#{app}-#{name}.target", binding
|
||||
process_master_names << "#{app}-#{name}.target"
|
||||
end
|
||||
|
||||
write_template "systemd/master.target.erb", "#{app}.target", binding
|
||||
end
|
||||
end
|
||||
@@ -1,49 +1,25 @@
|
||||
require "erb"
|
||||
require "foreman/configuration"
|
||||
require "foreman/export/base"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Upstart < Foreman::Export::Base
|
||||
|
||||
def export(location, options={})
|
||||
error("Must specify a location") unless location
|
||||
def export
|
||||
super
|
||||
|
||||
FileUtils.mkdir_p location
|
||||
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
|
||||
Dir["#{location}/#{app}*.conf"].each do |file|
|
||||
say "cleaning up: #{file}"
|
||||
FileUtils.rm(file)
|
||||
(Dir["#{location}/#{app}-*.conf"] << "#{location}/#{app}.conf").each do |file|
|
||||
clean file
|
||||
end
|
||||
|
||||
concurrency = parse_concurrency(options[:concurrency])
|
||||
write_template "upstart/master.conf.erb", "#{app}.conf", binding
|
||||
|
||||
master_template = export_template("upstart/master.conf.erb")
|
||||
master_config = ERB.new(master_template).result(binding)
|
||||
write_file "#{location}/#{app}.conf", master_config
|
||||
engine.each_process do |name, process|
|
||||
next if engine.formation[name] < 1
|
||||
write_template "upstart/process_master.conf.erb", "#{app}-#{name}.conf", binding
|
||||
|
||||
process_template = export_template("upstart/process.conf.erb")
|
||||
|
||||
engine.processes.values.each do |process|
|
||||
1.upto(concurrency[process.name]) do |num|
|
||||
process_config = ERB.new(process_template).result(binding)
|
||||
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
|
||||
1.upto(engine.formation[name]) do |num|
|
||||
port = engine.port_for(process, num)
|
||||
write_template "upstart/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
write_file "#{location}/#{app}.conf", <<-UPSTART_MASTER
|
||||
UPSTART_MASTER
|
||||
|
||||
engine.processes.each do |process|
|
||||
write_file process_conf, <<-UPSTART_CHILD
|
||||
UPSTART_CHILD
|
||||
end
|
||||
|
||||
engine.processes.each do |name, process|
|
||||
config.processes[name] ||= 1
|
||||
end
|
||||
config.write
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
45
lib/foreman/helpers.rb
Normal file
45
lib/foreman/helpers.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
module Foreman::Helpers
|
||||
# Copied whole sale from, https://github.com/defunkt/resque/
|
||||
|
||||
# Given a word with dashes, returns a camel cased version of it.
|
||||
#
|
||||
# classify('job-name') # => 'JobName'
|
||||
def classify(dashed_word)
|
||||
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
||||
end # Tries to find a constant with the name specified in the argument string:
|
||||
|
||||
#
|
||||
# constantize("Module") # => Module
|
||||
# constantize("Test::Unit") # => Test::Unit
|
||||
#
|
||||
# The name is assumed to be the one of a top-level constant, no matter
|
||||
# whether it starts with "::" or not. No lexical context is taken into
|
||||
# account:
|
||||
#
|
||||
# C = 'outside'
|
||||
# module M
|
||||
# C = 'inside'
|
||||
# C # => 'inside'
|
||||
# constantize("C") # => 'outside', same as ::C
|
||||
# end
|
||||
#
|
||||
# NameError is raised when the constant is unknown.
|
||||
def constantize(camel_cased_word)
|
||||
camel_cased_word = camel_cased_word.to_s
|
||||
|
||||
names = camel_cased_word.split('::')
|
||||
names.shift if names.empty? || names.first.empty?
|
||||
|
||||
constant = Object
|
||||
names.each do |name|
|
||||
args = Module.method(:const_get).arity != 1 ? [false] : []
|
||||
|
||||
if constant.const_defined?(name, *args)
|
||||
constant = constant.const_get(name)
|
||||
else
|
||||
constant = constant.const_missing(name)
|
||||
end
|
||||
end
|
||||
constant
|
||||
end
|
||||
end
|
||||
@@ -1,14 +1,129 @@
|
||||
require "foreman"
|
||||
require "shellwords"
|
||||
|
||||
class Foreman::Process
|
||||
|
||||
attr_reader :name
|
||||
attr_reader :command
|
||||
attr_accessor :color
|
||||
attr_reader :env
|
||||
|
||||
def initialize(name, command)
|
||||
@name = name
|
||||
# Create a Process
|
||||
#
|
||||
# @param [String] command The command to run
|
||||
# @param [Hash] options
|
||||
#
|
||||
# @option options [String] :cwd (./) Change to this working directory before executing the process
|
||||
# @option options [Hash] :env ({}) Environment variables to set for this process
|
||||
#
|
||||
def initialize(command, options={})
|
||||
@command = command
|
||||
@options = options.dup
|
||||
|
||||
@options[:env] ||= {}
|
||||
end
|
||||
|
||||
# Get environment-expanded command for a +Process+
|
||||
#
|
||||
# @param [Hash] custom_env ({}) Environment variables to merge with defaults
|
||||
#
|
||||
# @return [String] The expanded command
|
||||
#
|
||||
def expanded_command(custom_env={})
|
||||
env = @options[:env].merge(custom_env)
|
||||
expanded_command = command.dup
|
||||
env.each do |key, val|
|
||||
expanded_command.gsub!("$#{key}", val)
|
||||
end
|
||||
expanded_command
|
||||
end
|
||||
|
||||
# Run a +Process+
|
||||
#
|
||||
# @param [Hash] options
|
||||
#
|
||||
# @option options :env ({}) Environment variables to set for this execution
|
||||
# @option options :output ($stdout) The output stream
|
||||
#
|
||||
# @returns [Fixnum] pid The +pid+ of the process
|
||||
#
|
||||
def run(options={})
|
||||
env = @options[:env].merge(options[:env] || {})
|
||||
output = options[:output] || $stdout
|
||||
runner = "#{Foreman.runner}".shellescape
|
||||
|
||||
if Foreman.windows?
|
||||
Dir.chdir(cwd) do
|
||||
Process.spawn env, expanded_command(env), :out => output, :err => output
|
||||
end
|
||||
elsif Foreman.jruby_18? || Foreman.ruby_18?
|
||||
require "posix/spawn"
|
||||
wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{expanded_command(env)}"
|
||||
POSIX::Spawn.spawn(*spawn_args(env, wrapped_command.shellsplit, {:out => output, :err => output}))
|
||||
else
|
||||
wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{command}"
|
||||
Process.spawn env, wrapped_command, :out => output, :err => output
|
||||
end
|
||||
end
|
||||
|
||||
# Exec a +Process+
|
||||
#
|
||||
# @param [Hash] options
|
||||
#
|
||||
# @option options :env ({}) Environment variables to set for this execution
|
||||
#
|
||||
# @return Does not return
|
||||
def exec(options={})
|
||||
env = @options[:env].merge(options[:env] || {})
|
||||
env.each { |k, v| ENV[k] = v }
|
||||
Dir.chdir(cwd)
|
||||
Kernel.exec expanded_command(env)
|
||||
end
|
||||
|
||||
# Send a signal to this +Process+
|
||||
#
|
||||
# @param [String] signal The signal to send
|
||||
#
|
||||
def kill(signal)
|
||||
if Foreman.windows?
|
||||
pid && Process.kill(signal, pid)
|
||||
else
|
||||
pid && Process.kill("-#{signal}", pid)
|
||||
end
|
||||
rescue Errno::ESRCH
|
||||
false
|
||||
end
|
||||
|
||||
# Test whether or not this +Process+ is still running
|
||||
#
|
||||
# @returns [Boolean]
|
||||
#
|
||||
def alive?
|
||||
kill(0)
|
||||
end
|
||||
|
||||
# Test whether or not this +Process+ has terminated
|
||||
#
|
||||
# @returns [Boolean]
|
||||
#
|
||||
def dead?
|
||||
!alive?
|
||||
end
|
||||
|
||||
# Returns the working directory for this +Process+
|
||||
#
|
||||
# @returns [String]
|
||||
#
|
||||
def cwd
|
||||
File.expand_path(@options[:cwd] || ".")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def spawn_args(env, argv, options)
|
||||
args = []
|
||||
args << env
|
||||
args += argv
|
||||
args << options
|
||||
args
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
92
lib/foreman/procfile.rb
Normal file
92
lib/foreman/procfile.rb
Normal file
@@ -0,0 +1,92 @@
|
||||
require "foreman"
|
||||
|
||||
# Reads and writes Procfiles
|
||||
#
|
||||
# A valid Procfile entry is captured by this regex:
|
||||
#
|
||||
# /^([A-Za-z0-9_]+):\s*(.+)$/
|
||||
#
|
||||
# All other lines are ignored.
|
||||
#
|
||||
class Foreman::Procfile
|
||||
|
||||
# Initialize a Procfile
|
||||
#
|
||||
# @param [String] filename (nil) An optional filename to read from
|
||||
#
|
||||
def initialize(filename=nil)
|
||||
@entries = []
|
||||
load(filename) if filename
|
||||
end
|
||||
|
||||
# Yield each +Procfile+ entry in order
|
||||
#
|
||||
def entries(&blk)
|
||||
@entries.each do |(name, command)|
|
||||
yield name, command
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieve a +Procfile+ command by name
|
||||
#
|
||||
# @param [String] name The name of the Procfile entry to retrieve
|
||||
#
|
||||
def [](name)
|
||||
@entries.detect { |n,c| name == n }.last
|
||||
end
|
||||
|
||||
# Create a +Procfile+ entry
|
||||
#
|
||||
# @param [String] name The name of the +Procfile+ entry to create
|
||||
# @param [String] command The command of the +Procfile+ entry to create
|
||||
#
|
||||
def []=(name, command)
|
||||
delete name
|
||||
@entries << [name, command]
|
||||
end
|
||||
|
||||
# Remove a +Procfile+ entry
|
||||
#
|
||||
# @param [String] name The name of the +Procfile+ entry to remove
|
||||
#
|
||||
def delete(name)
|
||||
@entries.reject! { |n,c| name == n }
|
||||
end
|
||||
|
||||
# Load a Procfile from a file
|
||||
#
|
||||
# @param [String] filename The filename of the +Procfile+ to load
|
||||
#
|
||||
def load(filename)
|
||||
@entries.replace parse(filename)
|
||||
end
|
||||
|
||||
# Save a Procfile to a file
|
||||
#
|
||||
# @param [String] filename Save the +Procfile+ to this file
|
||||
#
|
||||
def save(filename)
|
||||
File.open(filename, 'w') do |file|
|
||||
file.puts self.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Get the +Procfile+ as a +String+
|
||||
#
|
||||
def to_s
|
||||
@entries.map do |name, command|
|
||||
[ name, command ].join(": ")
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse(filename)
|
||||
File.read(filename).gsub("\r\n","\n").split("\n").map do |line|
|
||||
if line =~ /^([A-Za-z0-9_-]+):\s*(.+)$/
|
||||
[$1, $2]
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
end
|
||||
5
lib/foreman/version.rb
Normal file
5
lib/foreman/version.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
module Foreman
|
||||
|
||||
VERSION = "0.63.0"
|
||||
|
||||
end
|
||||
186
man/foreman.1
186
man/foreman.1
@@ -1,19 +1,22 @@
|
||||
.\" generated with Ronn/v0.7.0
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.0
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "FOREMAN" "1" "June 2010" "Foreman 0.4.1" "Foreman Manual"
|
||||
.TH "FOREMAN" "1" "May 2013" "Foreman 0.63.0" "Foreman Manual"
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBforeman\fR \- manage Procfile\-based applications
|
||||
.
|
||||
.SH "SYNOPSIS"
|
||||
\fBforeman\fR start [process]
|
||||
\fBforeman start [process]\fR
|
||||
.
|
||||
.br
|
||||
\fBforeman\fR export \fIformat\fR [location]
|
||||
\fBforeman run <command>\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\.
|
||||
Foreman is a manager for Procfile\-based applications\. Its aim is to abstract away the details of the Procfile format, and allow you to either run your application directly or export it to some other process management format\.
|
||||
.
|
||||
.SH "RUNNING"
|
||||
\fBforeman start\fR is used to run your application directly from the command line\.
|
||||
@@ -28,8 +31,23 @@ If a parameter is passed, foreman will run one instance of the specified applica
|
||||
The following options control how the application is run:
|
||||
.
|
||||
.TP
|
||||
\fB\-s\fR, \fB\-\-screen\fR
|
||||
Run the application as a series of screen windows rather than interleaved in stdout\.
|
||||
\fB\-c\fR, \fB\-\-concurrency\fR
|
||||
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
|
||||
.
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-env\fR
|
||||
Specify one or more \.env files to load
|
||||
.
|
||||
.TP
|
||||
\fB\-f\fR, \fB\-\-procfile\fR
|
||||
Specify an alternate Procfile to load, implies \fB\-d\fR at the Procfile root\.
|
||||
.
|
||||
.TP
|
||||
\fB\-p\fR, \fB\-\-port\fR
|
||||
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
||||
.
|
||||
.P
|
||||
\fBforeman run\fR is used to run one\-off commands using the same environment as your defined processes\.
|
||||
.
|
||||
.SH "EXPORTING"
|
||||
\fBforeman export\fR is used to export your application to another process management format\.
|
||||
@@ -46,13 +64,157 @@ Use this name rather than the application\'s root directory name as the name of
|
||||
.
|
||||
.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\.
|
||||
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
|
||||
.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-l\fR, \fB\-\-log\fR
|
||||
Specify the directory to place process logs in\.
|
||||
.
|
||||
.TP
|
||||
\fB\-p\fR, \fB\-\-port\fR
|
||||
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
|
||||
.
|
||||
.TP
|
||||
\fB\-t\fR, \fB\-\-template\fR
|
||||
Specify an alternate template to use for creating export files\. See \fIhttps://github\.com/ddollar/foreman/tree/master/data/export\fR for examples\.
|
||||
.
|
||||
.TP
|
||||
\fB\-u\fR, \fB\-\-user\fR
|
||||
Specify the user the application should be run as\. Defaults to the app name
|
||||
.
|
||||
.SH "GLOBAL OPTIONS"
|
||||
These options control all modes of foreman\'s operation\.
|
||||
.
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-root\fR
|
||||
Specify an alternate application root\. This defaults to the directory containing the Procfile\.
|
||||
.
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-env\fR
|
||||
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
|
||||
.
|
||||
.TP
|
||||
\fB\-f\fR, \fB\-\-procfile\fR
|
||||
Specify an alternate location for the application\'s Procfile\. This file\'s containing directory will be assumed to be the root directory of the application\.
|
||||
.
|
||||
.SH "EXPORT FORMATS"
|
||||
foreman currently supports the following output formats:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
\fB\-p\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\.
|
||||
bluepill
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
inittab
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
launchd
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
runit
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
supervisord
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
systemd
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
upstart
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "INITTAB EXPORT"
|
||||
Will export a chunk of inittab\-compatible configuration:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
# \-\-\-\-\- foreman example processes \-\-\-\-\-
|
||||
EX01:4:respawn:/bin/su \- example \-c \'PORT=5000 bundle exec thin start >> /var/log/web\-1\.log 2>&1\'
|
||||
EX02:4:respawn:/bin/su \- example \-c \'PORT=5100 bundle exec rake jobs:work >> /var/log/job\-1\.log 2>&1\'
|
||||
# \-\-\-\-\- end foreman example processes \-\-\-\-\-
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "SYSTEMD EXPORT"
|
||||
Will create a series of systemd scripts in the location you specify\. Scripts will be structured to make the following commands valid:
|
||||
.
|
||||
.P
|
||||
\fBsystemctl start appname\.target\fR
|
||||
.
|
||||
.P
|
||||
\fBsystemctl stop appname\-processname\.target\fR
|
||||
.
|
||||
.P
|
||||
\fBsystemctl restart appname\-processname\-3\.service\fR
|
||||
.
|
||||
.SH "UPSTART EXPORT"
|
||||
Will create a series of upstart scripts in the location you specify\. Scripts will be structured to make the following commands valid:
|
||||
.
|
||||
.P
|
||||
\fBstart appname\fR
|
||||
.
|
||||
.P
|
||||
\fBstop appname\-processname\fR
|
||||
.
|
||||
.P
|
||||
\fBrestart appname\-processname\-3\fR
|
||||
.
|
||||
.SH "PROCFILE"
|
||||
A Procfile should contain both a name for the process and the command used to run it\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
web: bundle exec thin start
|
||||
job: bundle exec rake jobs:work
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.P
|
||||
A process name may contain letters, numbers and the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
$ foreman check
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "ENVIRONMENT"
|
||||
If a \fB\.env\fR file exists in the current directory, the default environment will be read from it\. This file should contain key/value pairs, separated by \fB=\fR, with one key/value pair per line\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
FOO=bar
|
||||
BAZ=qux
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "DEFAULT OPTIONS"
|
||||
If a \fB\.foreman\fR file exists in the current directory, default options will be read from it\. This file should be in YAML format with the long option name as keys\. Example:
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
concurrency: alpha=0,bravo=1
|
||||
port: 15000
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
@@ -89,7 +251,7 @@ Run one process type from the application defined in a specific Procfile:
|
||||
.
|
||||
.nf
|
||||
|
||||
$ foreman start alpha \-p ~/app/Procfile
|
||||
$ foreman start alpha \-f ~/myapp/Procfile
|
||||
.
|
||||
.fi
|
||||
.
|
||||
|
||||
@@ -3,19 +3,20 @@ foreman(1) -- manage Procfile-based applications
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`foreman` start [process]<br>
|
||||
`foreman` export <format> [location]
|
||||
`foreman start [process]`<br>
|
||||
`foreman run <command>`<br>
|
||||
`foreman export <format> [location]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**Foreman** is a manager for Procfile-based applications. Its aim is to
|
||||
Foreman is a manager for Procfile-based applications. Its aim is to
|
||||
abstract away the details of the Procfile format, and allow you to either run
|
||||
your application directly or export it to some other process management
|
||||
format.
|
||||
|
||||
## RUNNING
|
||||
|
||||
`foreman start` is used to run your application directly from the command line.
|
||||
`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.
|
||||
@@ -25,9 +26,22 @@ application type.
|
||||
|
||||
The following options control how the application is run:
|
||||
|
||||
* `-s`, `--screen`:
|
||||
Run the application as a series of screen windows rather than interleaved
|
||||
in stdout.
|
||||
* `-c`, `--concurrency`:
|
||||
Specify the number of each process type to run. The value passed in
|
||||
should be in the format `process=num,process=num`
|
||||
|
||||
* `-e`, `--env`:
|
||||
Specify one or more .env files to load
|
||||
|
||||
* `-f`, `--procfile`:
|
||||
Specify an alternate Procfile to load, implies `-d` at the Procfile root.
|
||||
|
||||
* `-p`, `--port`:
|
||||
Specify which port to use as the base for this application. Should be
|
||||
a multiple of 1000.
|
||||
|
||||
`foreman run` is used to run one-off commands using the same environment
|
||||
as your defined processes.
|
||||
|
||||
## EXPORTING
|
||||
|
||||
@@ -42,20 +56,123 @@ 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`.
|
||||
should be in the format `process=num,process=num`
|
||||
|
||||
## OPTIONS
|
||||
* `-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.
|
||||
|
||||
* `-t`, `--template`:
|
||||
Specify an alternate template to use for creating export files.
|
||||
See <https://github.com/ddollar/foreman/tree/master/data/export> for examples.
|
||||
|
||||
* `-u`, `--user`:
|
||||
Specify the user the application should be run as. Defaults to the
|
||||
app name
|
||||
|
||||
## GLOBAL OPTIONS
|
||||
|
||||
These options control all modes of foreman's operation.
|
||||
|
||||
* `-p`, `--procfile`
|
||||
* `-d`, `--root`:
|
||||
Specify an alternate application root. This defaults to the directory
|
||||
containing the Procfile.
|
||||
|
||||
* `-e`, `--env`:
|
||||
Specify an alternate environment file. You can specify more than one
|
||||
file by using: `--env file1,file2`.
|
||||
|
||||
* `-f`, `--procfile`:
|
||||
Specify an alternate location for the application's Procfile. This file's
|
||||
containing directory will be assumed to be the root directory of the
|
||||
application.
|
||||
|
||||
## EXPORT FORMATS
|
||||
|
||||
foreman currently supports the following output formats:
|
||||
|
||||
* bluepill
|
||||
|
||||
* inittab
|
||||
|
||||
* launchd
|
||||
|
||||
* runit
|
||||
|
||||
* supervisord
|
||||
|
||||
* systemd
|
||||
|
||||
* 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 -----
|
||||
|
||||
## SYSTEMD EXPORT
|
||||
|
||||
Will create a series of systemd scripts in the location you specify. Scripts
|
||||
will be structured to make the following commands valid:
|
||||
|
||||
`systemctl start appname.target`
|
||||
|
||||
`systemctl stop appname-processname.target`
|
||||
|
||||
`systemctl restart appname-processname-3.service`
|
||||
|
||||
## UPSTART EXPORT
|
||||
|
||||
Will create a series of upstart scripts in the location you specify. Scripts
|
||||
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 and the underscore character.
|
||||
You can validate your Procfile format using the `check` command:
|
||||
|
||||
$ foreman check
|
||||
|
||||
## ENVIRONMENT
|
||||
|
||||
If a `.env` file exists in the current directory, the default environment will
|
||||
be read from it. This file should contain key/value pairs, separated by `=`, with
|
||||
one key/value pair per line.
|
||||
|
||||
FOO=bar
|
||||
BAZ=qux
|
||||
|
||||
## DEFAULT OPTIONS
|
||||
|
||||
If a `.foreman` file exists in the current directory, default options will
|
||||
be read from it. This file should be in YAML format with the long option
|
||||
name as keys. Example:
|
||||
|
||||
concurrency: alpha=0,bravo=1
|
||||
port: 15000
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Start one instance of each process type, interleave the output on stdout:
|
||||
@@ -65,10 +182,10 @@ Start one instance of each process type, interleave the output on stdout:
|
||||
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 ~/app/Procfile
|
||||
$ foreman start alpha -f ~/myapp/Procfile
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
require "spec_helper"
|
||||
require "foreman/cli"
|
||||
|
||||
describe "Foreman::CLI" do
|
||||
describe "Foreman::CLI", :fakefs do
|
||||
subject { Foreman::CLI.new }
|
||||
|
||||
describe ".foreman" do
|
||||
before { File.open(".foreman", "w") { |f| f.puts "formation: alpha=2" } }
|
||||
|
||||
it "provides default options" do
|
||||
subject.send(:options)["formation"].should == "alpha=2"
|
||||
end
|
||||
|
||||
it "is overridden by options at the cli" do
|
||||
subject = Foreman::CLI.new([], :formation => "alpha=3")
|
||||
subject.send(:options)["formation"].should == "alpha=3"
|
||||
end
|
||||
end
|
||||
|
||||
describe "start" do
|
||||
describe "with a non-existent Procfile" do
|
||||
it "prints an error" do
|
||||
describe "when a Procfile doesnt exist", :fakefs do
|
||||
it "displays an error" do
|
||||
mock_error(subject, "Procfile does not exist.") do
|
||||
dont_allow.instance_of(Foreman::Engine).start
|
||||
subject.start
|
||||
@@ -14,51 +27,85 @@ describe "Foreman::CLI" do
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a Procfile" do
|
||||
before(:each) { write_procfile }
|
||||
|
||||
it "runs successfully" do
|
||||
dont_allow(subject).error
|
||||
mock.instance_of(Foreman::Engine).start
|
||||
subject.start
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "export" do
|
||||
describe "with a non-existent Procfile" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Procfile does not exist.") do
|
||||
dont_allow.instance_of(Foreman::Engine).export
|
||||
subject.export("testapp")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a Procfile" do
|
||||
before(:each) { write_procfile }
|
||||
|
||||
describe "with an invalid formatter" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Unknown export format: invalidformatter.") do
|
||||
subject.export("invalidformatter")
|
||||
end
|
||||
describe "with a valid Procfile" do
|
||||
it "can run a single command" do
|
||||
without_fakefs do
|
||||
output = foreman("start env -f #{resource_path("Procfile")}")
|
||||
output.should =~ /env.1/
|
||||
output.should_not =~ /test.1/
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a valid config" do
|
||||
before(:each) { write_foreman_config("testapp") }
|
||||
it "can run all commands" do
|
||||
without_fakefs do
|
||||
output = foreman("start -f #{resource_path("Procfile")} -e #{resource_path(".env")}")
|
||||
output.should =~ /echo.1 \| echoing/
|
||||
output.should =~ /env.1 \| bar/
|
||||
output.should =~ /test.1 \| testing/
|
||||
end
|
||||
end
|
||||
|
||||
it "runs successfully" do
|
||||
dont_allow(subject).error
|
||||
mock.instance_of(Foreman::Export::Upstart).export("/tmp/foo", {
|
||||
:concurrency => nil,
|
||||
:name => nil
|
||||
})
|
||||
subject.export("upstart", "/tmp/foo")
|
||||
it "sets PS variable with the process name" do
|
||||
without_fakefs do
|
||||
output = foreman("start -f #{resource_path("Procfile")}")
|
||||
output.should =~ /ps.1 \| PS env var is ps.1/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "check" do
|
||||
it "with a valid Procfile displays the jobs" do
|
||||
write_procfile
|
||||
foreman("check").should == "valid procfile detected (alpha, bravo, foo_bar, foo-bar)\n"
|
||||
end
|
||||
|
||||
it "with a blank Procfile displays an error" do
|
||||
FileUtils.touch "Procfile"
|
||||
foreman("check").should == "ERROR: no processes defined\n"
|
||||
end
|
||||
|
||||
it "without a Procfile displays an error" do
|
||||
FileUtils.rm_f "Procfile"
|
||||
foreman("check").should == "ERROR: Procfile does not exist.\n"
|
||||
end
|
||||
end
|
||||
|
||||
describe "run" do
|
||||
it "can run a command" do
|
||||
forked_foreman("run echo 1").should == "1\n"
|
||||
end
|
||||
|
||||
it "includes the environment" do
|
||||
forked_foreman("run #{resource_path("bin/env FOO")} -e #{resource_path(".env")}").should == "bar\n"
|
||||
end
|
||||
|
||||
it "can run a command from the Procfile" do
|
||||
forked_foreman("run -f #{resource_path("Procfile")} test").should == "testing\n"
|
||||
end
|
||||
|
||||
it "exits with the same exit code as the command" do
|
||||
fork_and_get_exitstatus("run echo 1").should == 0
|
||||
fork_and_get_exitstatus("run date 'invalid_date'").should == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "version" do
|
||||
it "displays gem version" do
|
||||
foreman("version").chomp.should == Foreman::VERSION
|
||||
end
|
||||
|
||||
it "displays gem version on shortcut command" do
|
||||
foreman("-v").chomp.should == Foreman::VERSION
|
||||
end
|
||||
end
|
||||
|
||||
describe "when posix-spawn is not present on ruby 1.8" do
|
||||
it "should fail with an error" do
|
||||
mock(Kernel).require('posix/spawn') { raise LoadError }
|
||||
output = foreman("start -f #{resource_path("Procfile")}")
|
||||
output.should == "ERROR: foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`.\n"
|
||||
end
|
||||
end if running_ruby_18?
|
||||
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -1,40 +1,112 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
|
||||
describe "Foreman::Engine" do
|
||||
subject { Foreman::Engine.new("Procfile") }
|
||||
class Foreman::Engine::Tester < Foreman::Engine
|
||||
attr_reader :buffer
|
||||
|
||||
def startup
|
||||
@buffer = ""
|
||||
end
|
||||
|
||||
def output(name, data)
|
||||
@buffer += "#{name}: #{data}"
|
||||
end
|
||||
|
||||
def shutdown
|
||||
end
|
||||
end
|
||||
|
||||
describe "Foreman::Engine", :fakefs do
|
||||
subject do
|
||||
write_procfile "Procfile"
|
||||
Foreman::Engine::Tester.new.load_procfile("Procfile")
|
||||
end
|
||||
|
||||
describe "initialize" do
|
||||
describe "without an existing Procfile" do
|
||||
it "raises an error" do
|
||||
lambda { subject }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a Procfile" do
|
||||
before { write_procfile }
|
||||
|
||||
it "reads the processes" do
|
||||
write_procfile
|
||||
subject.processes["alpha"].command.should == "./alpha"
|
||||
subject.processes["bravo"].command.should == "./bravo"
|
||||
subject.process("alpha").command.should == "./alpha"
|
||||
subject.process("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.process("alpha")).run(anything)
|
||||
mock(subject.process("bravo")).run(anything)
|
||||
mock(subject).watch_for_output
|
||||
mock(subject).watch_for_termination
|
||||
subject.start
|
||||
end
|
||||
|
||||
it "handles concurrency" do
|
||||
subject.options[:formation] = "alpha=2"
|
||||
mock(subject.process("alpha")).run(anything).twice
|
||||
mock(subject.process("bravo")).run(anything).never
|
||||
mock(subject).watch_for_output
|
||||
mock(subject).watch_for_termination
|
||||
subject.start
|
||||
end
|
||||
end
|
||||
|
||||
describe "execute" do
|
||||
it "runs the processes" do
|
||||
write_procfile
|
||||
mock(subject).run(subject.processes["alpha"], false)
|
||||
subject.execute("alpha")
|
||||
describe "directories" do
|
||||
it "has the directory default relative to the Procfile" do
|
||||
write_procfile "/some/app/Procfile"
|
||||
engine = Foreman::Engine.new.load_procfile("/some/app/Procfile")
|
||||
engine.root.should == "/some/app"
|
||||
end
|
||||
end
|
||||
|
||||
describe "environment" do
|
||||
it "should read env files" do
|
||||
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
|
||||
subject.load_env("/tmp/env")
|
||||
subject.env["FOO"].should == "baz"
|
||||
end
|
||||
|
||||
it "should read more than one if specified" do
|
||||
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
||||
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
||||
subject.load_env "/tmp/env1"
|
||||
subject.load_env "/tmp/env2"
|
||||
subject.env["FOO"].should == "bar"
|
||||
subject.env["BAZ"].should == "qux"
|
||||
end
|
||||
|
||||
it "should handle quoted values" do
|
||||
File.open("/tmp/env", "w") do |f|
|
||||
f.puts 'FOO=bar'
|
||||
f.puts 'BAZ="qux"'
|
||||
f.puts "FRED='barney'"
|
||||
f.puts 'OTHER="escaped\"quote"'
|
||||
end
|
||||
subject.load_env "/tmp/env"
|
||||
subject.env["FOO"].should == "bar"
|
||||
subject.env["BAZ"].should == "qux"
|
||||
subject.env["FRED"].should == "barney"
|
||||
subject.env["OTHER"].should == 'escaped"quote'
|
||||
end
|
||||
|
||||
it "should handle multiline strings" do
|
||||
File.open("/tmp/env", "w") do |f|
|
||||
f.puts 'FOO="bar\nbaz"'
|
||||
end
|
||||
subject.load_env "/tmp/env"
|
||||
subject.env["FOO"].should == "bar\nbaz"
|
||||
end
|
||||
|
||||
it "should fail if specified and doesnt exist" do
|
||||
lambda { subject.load_env "/tmp/env" }.should raise_error(Errno::ENOENT)
|
||||
end
|
||||
|
||||
it "should set port from .env if specified" do
|
||||
File.open("/tmp/env", "w") { |f| f.puts("PORT=9000") }
|
||||
subject.load_env "/tmp/env"
|
||||
subject.send(:base_port).should == 9000
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
19
spec/foreman/export/base_spec.rb
Normal file
19
spec/foreman/export/base_spec.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export"
|
||||
|
||||
describe "Foreman::Export::Base", :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:location) { "/tmp/init" }
|
||||
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
|
||||
let(:subject) { Foreman::Export::Base.new(location, engine) }
|
||||
|
||||
it "has a say method for displaying info" do
|
||||
mock(subject).puts("[foreman export] foo")
|
||||
subject.send(:say, "foo")
|
||||
end
|
||||
|
||||
it "raises errors as a Foreman::Export::Exception" do
|
||||
lambda { subject.send(:error, "foo") }.should raise_error(Foreman::Export::Exception, "foo")
|
||||
end
|
||||
end
|
||||
37
spec/foreman/export/bluepill_spec.rb
Normal file
37
spec/foreman/export/bluepill_spec.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/bluepill"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Bluepill, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:formation) { nil }
|
||||
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:bluepill) { Foreman::Export::Bluepill.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("bluepill") }
|
||||
before(:each) { stub(bluepill).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
bluepill.export
|
||||
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app.pill"))
|
||||
end
|
||||
|
||||
it "cleans up if exporting into an existing dir" do
|
||||
mock(FileUtils).rm("/tmp/init/app.pill")
|
||||
|
||||
bluepill.export
|
||||
bluepill.export
|
||||
end
|
||||
|
||||
context "with a process formation" do
|
||||
let(:formation) { "alpha=2" }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
bluepill.export
|
||||
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app-concurrency.pill"))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
97
spec/foreman/export/daemon_spec.rb
Normal file
97
spec/foreman/export/daemon_spec.rb
Normal file
@@ -0,0 +1,97 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/daemon"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Daemon, :fakefs do
|
||||
let(:procfile) { write_procfile("/tmp/app/Procfile") }
|
||||
let(:formation) { nil }
|
||||
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:daemon) { Foreman::Export::Daemon.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("daemon") }
|
||||
before(:each) { stub(daemon).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
daemon.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-bravo.conf").should == example_export_file("daemon/app-bravo.conf")
|
||||
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("daemon/app-bravo-1.conf")
|
||||
end
|
||||
|
||||
it "cleans up if exporting into an existing dir" do
|
||||
mock(FileUtils).rm("/tmp/init/app.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-alpha.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-alpha-1.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-bravo.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-bravo-1.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo-bar.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo_bar.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.conf")
|
||||
|
||||
daemon.export
|
||||
daemon.export
|
||||
end
|
||||
|
||||
it "does not delete exported files for similarly named applications" do
|
||||
FileUtils.mkdir_p "/tmp/init"
|
||||
|
||||
["app2", "app2-alpha", "app2-alpha-1"].each do |name|
|
||||
path = "/tmp/init/#{name}.conf"
|
||||
FileUtils.touch(path)
|
||||
dont_allow(FileUtils).rm(path)
|
||||
end
|
||||
|
||||
daemon.export
|
||||
end
|
||||
|
||||
context "with a formation" do
|
||||
let(:formation) { "alpha=2" }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
daemon.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("daemon/app-alpha-2.conf")
|
||||
File.exists?("/tmp/init/app-bravo-1.conf").should == false
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates" do
|
||||
let(:template) { "/tmp/alternate" }
|
||||
let(:options) { { :app => "app", :template => template } }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p template
|
||||
File.open("#{template}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
daemon.export
|
||||
File.read("/tmp/init/app.conf").should == "alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates from home dir" do
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/daemon")
|
||||
File.open(File.expand_path("~/.foreman/templates/daemon/master.conf.erb"), "w") do |file|
|
||||
file.puts "default_alternate_template"
|
||||
end
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
daemon.export
|
||||
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
40
spec/foreman/export/inittab_spec.rb
Normal file
40
spec/foreman/export/inittab_spec.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/inittab"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Inittab, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:location) { "/tmp/inittab" }
|
||||
let(:formation) { nil }
|
||||
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:inittab) { Foreman::Export::Inittab.new(location, engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("inittab") }
|
||||
before(:each) { stub(inittab).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
inittab.export
|
||||
File.read("/tmp/inittab").should == example_export_file("inittab/inittab.default")
|
||||
end
|
||||
|
||||
context "to stdout" do
|
||||
let(:location) { "-" }
|
||||
|
||||
it "exports to stdout" do
|
||||
mock(inittab).puts example_export_file("inittab/inittab.default")
|
||||
inittab.export
|
||||
end
|
||||
end
|
||||
|
||||
context "with concurrency" do
|
||||
let(:formation) { "alpha=2" }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
inittab.export
|
||||
File.read("/tmp/inittab").should == example_export_file("inittab/inittab.concurrency")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
31
spec/foreman/export/launchd_spec.rb
Normal file
31
spec/foreman/export/launchd_spec.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/launchd"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Launchd, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:options) { Hash.new }
|
||||
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
|
||||
let(:launchd) { Foreman::Export::Launchd.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("launchd") }
|
||||
before(:each) { stub(launchd).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
launchd.export
|
||||
File.read("/tmp/init/app-alpha-1.plist").should == example_export_file("launchd/launchd-a.default")
|
||||
File.read("/tmp/init/app-bravo-1.plist").should == example_export_file("launchd/launchd-b.default")
|
||||
end
|
||||
|
||||
context "with multiple command arguments" do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", "charlie") }
|
||||
|
||||
it "splits each command argument" do
|
||||
launchd.export
|
||||
File.read("/tmp/init/app-alpha-1.plist").should == example_export_file("launchd/launchd-c.default")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
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, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
|
||||
let(:engine) { Foreman::Engine.new(:formation => "alpha=2,bravo=1").load_procfile(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("runit") }
|
||||
before(:each) { stub(runit).say }
|
||||
before(:each) { stub(FakeFS::FileUtils).chmod }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
engine.env["BAR"] = "baz"
|
||||
runit.export
|
||||
|
||||
File.read("/tmp/init/app-alpha-1/run").should == example_export_file('runit/app-alpha-1/run')
|
||||
File.read("/tmp/init/app-alpha-1/log/run").should == example_export_file('runit/app-alpha-1/log/run')
|
||||
File.read("/tmp/init/app-alpha-1/env/PORT").should == "5000\n"
|
||||
File.read("/tmp/init/app-alpha-1/env/BAR").should == "baz\n"
|
||||
File.read("/tmp/init/app-alpha-2/run").should == example_export_file('runit/app-alpha-2/run')
|
||||
File.read("/tmp/init/app-alpha-2/log/run").should == example_export_file('runit/app-alpha-2/log/run')
|
||||
File.read("/tmp/init/app-alpha-2/env/PORT").should == "5001\n"
|
||||
File.read("/tmp/init/app-alpha-2/env/BAR").should == "baz\n"
|
||||
File.read("/tmp/init/app-bravo-1/run").should == example_export_file('runit/app-bravo-1/run')
|
||||
File.read("/tmp/init/app-bravo-1/log/run").should == example_export_file('runit/app-bravo-1/log/run')
|
||||
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
|
||||
end
|
||||
|
||||
it "creates a full path to the export directory" do
|
||||
expect { runit.export }.to_not raise_error
|
||||
end
|
||||
end
|
||||
36
spec/foreman/export/supervisord_spec.rb
Normal file
36
spec/foreman/export/supervisord_spec.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/supervisord"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Supervisord, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:formation) { nil }
|
||||
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:supervisord) { Foreman::Export::Supervisord.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("supervisord") }
|
||||
before(:each) { stub(supervisord).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
supervisord.export
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("supervisord/app-alpha-1.conf")
|
||||
end
|
||||
|
||||
it "cleans up if exporting into an existing dir" do
|
||||
mock(FileUtils).rm("/tmp/init/app.conf")
|
||||
supervisord.export
|
||||
supervisord.export
|
||||
end
|
||||
|
||||
context "with concurrency" do
|
||||
let(:formation) { "alpha=2" }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
supervisord.export
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("supervisord/app-alpha-2.conf")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
91
spec/foreman/export/systemd_spec.rb
Normal file
91
spec/foreman/export/systemd_spec.rb
Normal file
@@ -0,0 +1,91 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/systemd"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Systemd, :fakefs do
|
||||
let(:procfile) { write_procfile("/tmp/app/Procfile") }
|
||||
let(:formation) { nil }
|
||||
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:systemd) { Foreman::Export::Systemd.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("systemd") }
|
||||
before(:each) { stub(systemd).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
systemd.export
|
||||
|
||||
File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
|
||||
File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
|
||||
File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
|
||||
File.read("/tmp/init/app-bravo.target").should == example_export_file("systemd/app-bravo.target")
|
||||
File.read("/tmp/init/app-bravo-1.service").should == example_export_file("systemd/app-bravo-1.service")
|
||||
end
|
||||
|
||||
it "cleans up if exporting into an existing dir" do
|
||||
mock(FileUtils).rm("/tmp/init/app.target")
|
||||
mock(FileUtils).rm("/tmp/init/app-alpha.target")
|
||||
mock(FileUtils).rm("/tmp/init/app-alpha-1.service")
|
||||
mock(FileUtils).rm("/tmp/init/app-bravo.target")
|
||||
mock(FileUtils).rm("/tmp/init/app-bravo-1.service")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo-bar.target")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.service")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo_bar.target")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.service")
|
||||
|
||||
systemd.export
|
||||
systemd.export
|
||||
end
|
||||
|
||||
it "includes environment variables" do
|
||||
engine.env['KEY'] = 'some "value"'
|
||||
systemd.export
|
||||
File.read("/tmp/init/app-alpha-1.service").should =~ /KEY=some "value"$/
|
||||
end
|
||||
|
||||
context "with a formation" do
|
||||
let(:formation) { "alpha=2" }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
systemd.export
|
||||
|
||||
File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
|
||||
File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
|
||||
File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
|
||||
File.read("/tmp/init/app-alpha-2.service").should == example_export_file("systemd/app-alpha-2.service")
|
||||
File.exists?("/tmp/init/app-bravo-1.service").should == false
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates" do
|
||||
let(:template) { "/tmp/alternate" }
|
||||
let(:options) { { :app => "app", :template => template } }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p template
|
||||
File.open("#{template}/master.target.erb", "w") { |f| f.puts "alternate_template" }
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
systemd.export
|
||||
File.read("/tmp/init/app.target").should == "alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates from home dir" do
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/systemd")
|
||||
File.open(File.expand_path("~/.foreman/templates/systemd/master.target.erb"), "w") do |file|
|
||||
file.puts "default_alternate_template"
|
||||
end
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
systemd.export
|
||||
File.read("/tmp/init/app.target").should == "default_alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,2 +1,104 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/upstart"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Upstart, :fakefs do
|
||||
let(:procfile) { write_procfile("/tmp/app/Procfile") }
|
||||
let(:formation) { nil }
|
||||
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("upstart") }
|
||||
before(:each) { stub(upstart).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
upstart.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-bravo.conf").should == example_export_file("upstart/app-bravo.conf")
|
||||
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf")
|
||||
end
|
||||
|
||||
it "cleans up if exporting into an existing dir" do
|
||||
mock(FileUtils).rm("/tmp/init/app.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-alpha.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-alpha-1.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-bravo.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-bravo-1.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo-bar.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo_bar.conf")
|
||||
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.conf")
|
||||
|
||||
upstart.export
|
||||
upstart.export
|
||||
end
|
||||
|
||||
it "does not delete exported files for similarly named applications" do
|
||||
FileUtils.mkdir_p "/tmp/init"
|
||||
|
||||
["app2", "app2-alpha", "app2-alpha-1"].each do |name|
|
||||
path = "/tmp/init/#{name}.conf"
|
||||
FileUtils.touch(path)
|
||||
dont_allow(FileUtils).rm(path)
|
||||
end
|
||||
|
||||
upstart.export
|
||||
end
|
||||
|
||||
it "quotes and escapes environment variables" do
|
||||
engine.env['KEY'] = 'd"\|d'
|
||||
upstart.export
|
||||
"foobarfoo".should include "bar"
|
||||
File.read("/tmp/init/app-alpha-1.conf").should =~ /KEY=d\\"\\\\\\\|d/
|
||||
end
|
||||
|
||||
context "with a formation" do
|
||||
let(:formation) { "alpha=2" }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
upstart.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("upstart/app-alpha-2.conf")
|
||||
File.exists?("/tmp/init/app-bravo-1.conf").should == false
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates" do
|
||||
let(:template) { "/tmp/alternate" }
|
||||
let(:options) { { :app => "app", :template => template } }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p template
|
||||
File.open("#{template}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
upstart.export
|
||||
File.read("/tmp/init/app.conf").should == "alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates from home dir" do
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/upstart")
|
||||
File.open(File.expand_path("~/.foreman/templates/upstart/master.conf.erb"), "w") do |file|
|
||||
file.puts "default_alternate_template"
|
||||
end
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
upstart.export
|
||||
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,2 +1,24 @@
|
||||
require "spec_helper"
|
||||
require "foreman/export"
|
||||
|
||||
describe "Foreman::Export" do
|
||||
subject { Foreman::Export }
|
||||
|
||||
describe "with a formatter that doesn't declare the appropriate class" do
|
||||
it "prints an error" do
|
||||
mock(subject).require("foreman/export/invalidformatter")
|
||||
mock_export_error("Unknown export format: invalidformatter (no class Foreman::Export::Invalidformatter).") do
|
||||
subject.formatter("invalidformatter")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "with an invalid formatter" do
|
||||
|
||||
it "prints an error" do
|
||||
mock_export_error("Unknown export format: invalidformatter (unable to load file 'foreman/export/invalidformatter').") do
|
||||
subject.formatter("invalidformatter")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
26
spec/foreman/helpers_spec.rb
Normal file
26
spec/foreman/helpers_spec.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
require "spec_helper"
|
||||
require "foreman/helpers"
|
||||
|
||||
describe "Foreman::Helpers" do
|
||||
before do
|
||||
module Foo
|
||||
class Bar; end
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
Object.send(:remove_const, :Foo)
|
||||
end
|
||||
|
||||
subject { o = Object.new; o.extend(Foreman::Helpers); o }
|
||||
|
||||
it "should classify words" do
|
||||
subject.classify("foo").should == "Foo"
|
||||
subject.classify("foo-bar").should == "FooBar"
|
||||
end
|
||||
|
||||
it "should constantize words" do
|
||||
subject.constantize("Object").should == Object
|
||||
subject.constantize("Foo::Bar").should == Foo::Bar
|
||||
end
|
||||
end
|
||||
@@ -1,2 +1,48 @@
|
||||
require "spec_helper"
|
||||
require "foreman/process"
|
||||
require 'spec_helper'
|
||||
require 'foreman/process'
|
||||
require 'ostruct'
|
||||
require 'timeout'
|
||||
require 'tmpdir'
|
||||
|
||||
describe Foreman::Process do
|
||||
|
||||
def run(process, options={})
|
||||
rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
|
||||
process.run(options.merge(:output => wr))
|
||||
rd.gets
|
||||
end
|
||||
|
||||
describe "#run" do
|
||||
|
||||
it "runs the process" do
|
||||
process = Foreman::Process.new(resource_path("bin/test"))
|
||||
run(process).should == "testing\n"
|
||||
end
|
||||
|
||||
it "can set environment" do
|
||||
process = Foreman::Process.new(resource_path("bin/env FOO"), :env => { "FOO" => "bar" })
|
||||
run(process).should == "bar\n"
|
||||
end
|
||||
|
||||
it "can set per-run environment" do
|
||||
process = Foreman::Process.new(resource_path("bin/env FOO"))
|
||||
run(process, :env => { "FOO" => "bar "}).should == "bar\n"
|
||||
end
|
||||
|
||||
it "can handle env vars in the command" do
|
||||
process = Foreman::Process.new(resource_path("bin/echo $FOO"), :env => { "FOO" => "bar" })
|
||||
run(process).should == "bar\n"
|
||||
end
|
||||
|
||||
it "can handle per-run env vars in the command" do
|
||||
process = Foreman::Process.new(resource_path("bin/echo $FOO"))
|
||||
run(process, :env => { "FOO" => "bar" }).should == "bar\n"
|
||||
end
|
||||
|
||||
it "should output utf8 properly" do
|
||||
process = Foreman::Process.new(resource_path("bin/utf8"))
|
||||
run(process).should == (Foreman.ruby_18? ? "\xFF\x03\n" : "\xFF\x03\n".force_encoding('binary'))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
43
spec/foreman/procfile_spec.rb
Normal file
43
spec/foreman/procfile_spec.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
require 'spec_helper'
|
||||
require 'foreman/procfile'
|
||||
require 'pathname'
|
||||
require 'tmpdir'
|
||||
|
||||
describe Foreman::Procfile, :fakefs do
|
||||
subject { Foreman::Procfile.new }
|
||||
|
||||
it "can load from a file" do
|
||||
write_procfile
|
||||
subject.load "Procfile"
|
||||
subject["alpha"].should == "./alpha"
|
||||
subject["bravo"].should == "./bravo"
|
||||
end
|
||||
|
||||
it "loads a passed-in Procfile" do
|
||||
write_procfile
|
||||
procfile = Foreman::Procfile.new("Procfile")
|
||||
procfile["alpha"].should == "./alpha"
|
||||
procfile["bravo"].should == "./bravo"
|
||||
procfile["foo-bar"].should == "./foo-bar"
|
||||
procfile["foo_bar"].should == "./foo_bar"
|
||||
end
|
||||
|
||||
it "can have a process appended to it" do
|
||||
subject["charlie"] = "./charlie"
|
||||
subject["charlie"].should == "./charlie"
|
||||
end
|
||||
|
||||
it "can write to a string" do
|
||||
subject["foo"] = "./foo"
|
||||
subject["bar"] = "./bar"
|
||||
subject.to_s.should == "foo: ./foo\nbar: ./bar"
|
||||
end
|
||||
|
||||
it "can write to a file" do
|
||||
subject["foo"] = "./foo"
|
||||
subject["bar"] = "./bar"
|
||||
subject.save "/tmp/proc"
|
||||
File.read("/tmp/proc").should == "foo: ./foo\nbar: ./bar\n"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -8,4 +8,9 @@ describe Foreman do
|
||||
it { should be_a String }
|
||||
end
|
||||
|
||||
describe "runner" do
|
||||
it "should exist" do
|
||||
File.exists?(Foreman.runner).should == true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
1
spec/resources/.env
Normal file
1
spec/resources/.env
Normal file
@@ -0,0 +1 @@
|
||||
FOO=bar
|
||||
5
spec/resources/Procfile
Normal file
5
spec/resources/Procfile
Normal file
@@ -0,0 +1,5 @@
|
||||
echo: bin/echo echoing
|
||||
env: bin/env FOO
|
||||
test: bin/test
|
||||
utf8: bin/utf8
|
||||
ps: bin/echo PS env var is $PS
|
||||
2
spec/resources/bin/echo
Executable file
2
spec/resources/bin/echo
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
echo $*
|
||||
2
spec/resources/bin/env
Executable file
2
spec/resources/bin/env
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
echo ${!1}
|
||||
2
spec/resources/bin/test
Executable file
2
spec/resources/bin/test
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
echo "testing"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user