Compare commits

..

211 Commits

Author SHA1 Message Date
David Dollar
501bc138c5 authors 2012-02-07 11:18:06 -05:00
David Dollar
df8c05cd6c 0.39.0 2012-02-07 11:17:59 -05:00
David Dollar
edcc4f3567 move to foreman-runner 2012-02-07 11:17:44 -05:00
David Dollar
3759dbb463 fix tgz release 2012-02-07 11:16:07 -05:00
David Dollar
b673931c05 Double Revert "Revert "bundle update hpricot""
This reverts commit 137e43b040.
2012-02-04 19:47:48 -05:00
David Dollar
137e43b040 Revert "bundle update hpricot"
This reverts commit 16d4b84a5d.
2012-02-04 19:25:01 -05:00
David Dollar
79211d9bbf Merge pull request #147 from jfirebaugh/rubinius
bundle update hpricot
2012-02-04 14:05:06 -08:00
John Firebaugh
16d4b84a5d bundle update hpricot
0.8.2 doesn't compile on Rubinius; 0.8.6 does.
2012-02-04 13:46:48 -08:00
David Dollar
5ea4537046 remove version from inner pkg 2012-02-02 21:31:58 -05:00
David Dollar
d51433ff82 fix foreman pkg builder 2012-02-02 21:08:01 -05:00
David Dollar
54ab74d305 fix release tasks 2012-02-02 17:32:52 -05:00
David Dollar
e8b8f34f41 changelog 2012-02-02 17:27:56 -05:00
David Dollar
7535f1d3d8 0.38.0 2012-02-02 17:26:42 -05:00
David Dollar
3af0dfb4ae bring back single process starting 2012-02-02 17:26:25 -05:00
David Dollar
78547b8175 foreman + jruby + travis = nope 2012-02-01 13:24:43 -05:00
David Dollar
976fbc0bb0 stub out proctitle/termtitle 2012-02-01 10:19:33 -05:00
David Dollar
28a9aa774f try ci on jruby 2012-02-01 10:06:25 -05:00
David Dollar
51f5ff3842 latest jruby fixed non-excutable to raise EACCESS 2012-01-31 18:01:53 -05:00
David Dollar
e1e18f62bb remove jruby until we figure out what's going on with travis 2012-01-30 11:42:41 -05:00
David Dollar
0a09117328 set JRUBY_OPTS for travis 2012-01-29 23:02:48 -05:00
David Dollar
c3df12746f ignore .rbenv-version 2012-01-29 23:02:40 -05:00
David Dollar
2ec6a23fb3 force to binary encoding if supported 2012-01-29 23:02:20 -05:00
David Dollar
0d6b784de1 disable test in jruby 2012-01-29 23:02:10 -05:00
David Dollar
2dcd2c03db wait for process termination instead of sleeping 2012-01-29 23:02:03 -05:00
David Dollar
9d6d0bbb7d add bundler setup to rakefile 2012-01-29 22:45:50 -05:00
David Dollar
89c1314abe try jruby with travis 2012-01-29 22:37:07 -05:00
David Dollar
f33211d100 clean up gems 2012-01-29 21:35:51 -05:00
David Dollar
4317079bf3 tweak changelog 2012-01-29 21:29:11 -05:00
David Dollar
c745c282c9 update docs 2012-01-29 21:28:49 -05:00
David Dollar
7dca45db57 0.37.2 2012-01-29 21:28:30 -05:00
David Dollar
de3c47fe21 handle directories with spaces in runner 2012-01-29 21:28:11 -05:00
David Dollar
a8a255db4a update docs 2012-01-29 14:06:09 -05:00
David Dollar
307d63b631 changelog 2012-01-29 14:05:59 -05:00
David Dollar
de54f6a5a5 0.37.1 2012-01-29 14:03:45 -05:00
David Dollar
0dff116340 use a 1.8-compatbiel method for IO.pipe 2012-01-29 14:01:13 -05:00
David Dollar
e053dc8434 cleanup 2012-01-29 13:58:16 -05:00
David Dollar
20f6ba1563 use binary pipes 2012-01-29 13:56:57 -05:00
David Dollar
557a08ea77 add utf8 test to engine 2012-01-29 13:56:51 -05:00
David Dollar
8eccc819d6 remove nonfunctional utf8 test 2012-01-29 13:56:42 -05:00
David Dollar
347d4a0184 factor out poll_readers 2012-01-29 13:56:35 -05:00
David Dollar
0147f5d284 set up example procfile with utf8 item 2012-01-29 13:56:22 -05:00
David Dollar
1da034ce66 try to add failing test for utf8 2012-01-29 10:03:31 -05:00
David Dollar
5fc7552572 refactor resource_file 2012-01-29 10:03:14 -05:00
David Dollar
083efe3ae9 sleep longer after loading scripts 2012-01-29 10:03:03 -05:00
David Dollar
9d2382a2d2 remove autotest 2012-01-29 02:06:01 -05:00
David Dollar
f731daae1a update docs 2012-01-29 01:55:44 -05:00
David Dollar
b7f0e3f573 typo 2012-01-29 01:55:14 -05:00
David Dollar
e7c523dab7 cleanup 2012-01-29 01:52:30 -05:00
David Dollar
9f4f15a29c update docs 2012-01-29 01:51:52 -05:00
David Dollar
b2dc89c50e fix up authors 2012-01-29 01:51:00 -05:00
David Dollar
389bf05800 cleanup 2012-01-29 01:47:05 -05:00
David Dollar
a632a62efd update docs 2012-01-29 01:45:57 -05:00
David Dollar
2edf6e1c68 pedantry 2012-01-29 01:41:21 -05:00
David Dollar
56158e881b add changelog 2012-01-29 01:40:36 -05:00
David Dollar
6b5ed495d5 fix up packaging after moving tasks 2012-01-29 01:38:21 -05:00
David Dollar
9652c285f1 fix up changelog task 2012-01-29 01:32:20 -05:00
David Dollar
ba171cc10d curate changelog 2012-01-29 01:26:21 -05:00
David Dollar
ebb191adba better changelog task 2012-01-29 01:26:14 -05:00
David Dollar
8c3ef1a7af 0.37.0 2012-01-29 01:13:27 -05:00
David Dollar
cf69c31ae3 raw changelog 2012-01-29 01:13:02 -05:00
David Dollar
e16a35da4b fix changelog script 2012-01-29 01:12:56 -05:00
David Dollar
2490dd2a5b put an entire line of output inside a single mutex so we don't cross the streams 2012-01-29 00:09:44 -05:00
David Dollar
2705520496 fix race condition with process termination 2012-01-29 00:09:28 -05:00
David Dollar
522fee5e9e pedantry 2012-01-29 00:08:58 -05:00
David Dollar
c462473a25 fix simplecov 2012-01-28 18:53:43 -05:00
David Dollar
224fe94dc2 add more tests 2012-01-28 18:48:26 -05:00
David Dollar
7cb73f5c36 rearrange tasks, add coverage 2012-01-28 18:48:13 -05:00
David Dollar
1537ddbcc9 tweak simplecov entry 2012-01-28 16:27:02 -05:00
David Dollar
b04c81dd06 add simplecov 2012-01-28 16:26:12 -05:00
David Dollar
ae22d34967 Merge pull request #144 from technomancy/debian
Drop the S3 publishing rake tasks.
2012-01-26 17:02:42 -08:00
Phil Hagelberg
9a359efbf7 Drop the S3 publishing rake tasks. 2012-01-26 17:00:07 -08:00
David Dollar
9632227d29 0.37.0.pre5 2012-01-24 08:34:48 -08:00
David Dollar
288d118ca4 Merge pull request #142 from apollo13/master
Fix bash string comparision
2012-01-24 08:29:38 -08:00
David Dollar
576455b8d7 rename readme 2012-01-24 08:14:50 -08:00
David Dollar
a04032454a 0.37.0.pre4 2012-01-24 08:03:07 -08:00
David Dollar
29c94c785d Merge pull request #130 from clowder/foreman
---

Because `Your::Runit::Env != My::Runit::Env` I thought that it would be nice to be able to include your own export format as a another gem along side Foreman; similar to MultiJson’s engines but using constantize because we are specifying formats via the command line.

Cheers,
Chris
2012-01-24 08:00:12 -08:00
Chris Lowder
a0a2dd9454 Don't expose the options hash. 2012-01-24 15:10:25 +00:00
Chris Lowder
1b701cddf3 Simplify subclassing by adding all arguments to the initializer. Also, clean up the method signatures for existing exporters. 2012-01-24 15:10:18 +00:00
Chris Lowder
4080b3f1f2 Add ZenTest as a development dependency, there is an autotest folder but the gem is missing. 2012-01-24 14:37:57 +00:00
Chris Lowder
84c49ae2b8 Attempt to require the custom export class. 2012-01-24 14:36:38 +00:00
Chris Lowder
d54b46806c Catching more than we need to. 2012-01-24 14:36:38 +00:00
Chris Lowder
bc1c5e4c74 Extract commonality into the base class, make life easy for our plugin writers. 2012-01-24 14:36:38 +00:00
Chris Lowder
ed4a32518f Removing the hard coding of export formats allowing the user to 'plug-in' their own export format. 2012-01-24 14:31:45 +00:00
Florian Apolloner
e1d3955d3c Fix the test for an empty string in bin/runner 2012-01-24 09:51:16 +01:00
David Dollar
c7167e1c83 Update README.markdown 2012-01-23 14:20:45 -08:00
David Dollar
db93432118 Revert "try on more rubies"
This reverts commit 5eada6b7cc.
2012-01-23 00:45:25 -05:00
David Dollar
5eada6b7cc try on more rubies 2012-01-23 00:43:25 -05:00
David Dollar
5e46b1f43a fix webhook url 2012-01-23 00:41:10 -05:00
David Dollar
ef723f5831 Update README.markdown 2012-01-23 00:37:58 -05:00
David Dollar
52e24ef64b Update README.markdown 2012-01-23 00:31:57 -05:00
David Dollar
0b1daf1927 ensure we have non-nil data, fixes #111 2012-01-22 22:01:03 -05:00
David Dollar
e0d84a56d7 make sure error method exists. fixes #104 2012-01-22 21:55:55 -05:00
David Dollar
7ee2edcc60 Merge pull request #136 from fnichol/foreman
---

Simple enough, but this helps when re-exporting runit services. Cheers, and thanks!

Conflicts:
	spec/foreman/export/runit_spec.rb
2012-01-22 21:46:57 -05:00
David Dollar
d428ac5356 remove other from install instructions 2012-01-22 21:42:51 -05:00
David Dollar
5351b49fee cleanup 2012-01-22 21:41:10 -05:00
David Dollar
6ebe76d8c1 Revert "tweak authors"
This reverts commit 7c43c672c9
2012-01-22 21:39:24 -05:00
David Dollar
7c43c672c9 tweak authors 2012-01-22 21:39:09 -05:00
David Dollar
41d9050ae3 fix authors 2012-01-22 21:38:17 -05:00
David Dollar
09e9cefa3a readme tweaks 2012-01-22 21:36:38 -05:00
David Dollar
85efe5c1ba readme tweaks 2012-01-22 21:36:24 -05:00
David Dollar
c21634c04e fix up authors 2012-01-22 21:34:23 -05:00
David Dollar
6d4f3476f1 Update README.markdown 2012-01-22 21:32:33 -05:00
David Dollar
724812d6e3 fix up specs 2012-01-22 21:17:54 -05:00
David Dollar
ce6ec4848d we're not chdiring any more 2012-01-22 21:17:38 -05:00
David Dollar
a37a097f73 use strings rather than symbols to better emulate the real thing 2012-01-22 21:17:18 -05:00
David Dollar
f28725bdac Merge pull request #139 from brainopia/foreman
---

If you remember Ive wanted to implement a prototype of process-tree monitor (and therefore restore support for complex commands), but decided to start with specing current behavior and marking desired behavior as pending specs.

This pull-request is a first pass. Its mainly Foreman::Process specs and a bit of refactoring. Specs for Foreman::Engine will be coming next (theyll cover a wide ground of possible uses). Afterwards I will replace machinery a bit to remove pending specs and ensure the rest is still passing on all available to me platforms.

If you have any notices, Ill gladly hear them.
2012-01-22 20:58:51 -05:00
David Dollar
51eee011ce 0.37.0.pre3 2012-01-22 20:40:50 -05:00
David Dollar
6c8c848f54 normalize platform names 2012-01-22 20:33:14 -05:00
David Dollar
f60c4cb767 windows support 2012-01-22 20:33:14 -05:00
David Dollar
4ad49cb058 add windows support 2012-01-22 20:33:13 -05:00
David Dollar
e993af2b20 dont need pty 2012-01-22 20:33:13 -05:00
David Dollar
d7000bccfa fix specs 2012-01-22 19:14:24 -05:00
David Dollar
2995a605b4 0.37.0.pre2 2012-01-22 19:05:42 -05:00
David Dollar
ef280e802d 0.37.0.pre1 2012-01-22 19:04:25 -05:00
David Dollar
8a9001842c remove unnecessary stdout/stderr flattening 2012-01-22 19:01:36 -05:00
David Dollar
8a8d31eb43 use PLATFORM=jruby instead of JRUBY=true 2012-01-22 18:55:47 -05:00
David Dollar
3e98170878 fix java build bug 2012-01-22 18:39:43 -05:00
David Dollar
3d84de3062 dont do rubygems/bundler in the Rakefile 2012-01-22 18:39:33 -05:00
David Dollar
9cc0afca49 switch to posix-spawn for jruby 2012-01-22 18:39:20 -05:00
David Dollar
7a25d3ac5a add jruby build 2012-01-22 17:38:02 -05:00
David Dollar
db1a5df354 spork is in the gemspec now 2012-01-22 17:37:41 -05:00
David Dollar
e137596ce0 remove debugging 2012-01-22 17:37:30 -05:00
David Dollar
c9042c5aae move the spoon require into the jruby branch 2012-01-22 17:30:02 -05:00
David Dollar
550adc8070 pass basedir along to the runner script 2012-01-22 17:29:52 -05:00
David Dollar
fbdde3e62a beef up the runner script to allow a working directory to be set 2012-01-22 17:29:15 -05:00
David Dollar
e161ecb630 Merge pull request #140 from jc00ke/foreman
---

Fixes #2

This is my first patch for JRuby, so any feedback would be appreciated. The specs do not run (Ill file a separate issue) but I am able to successfully start up & run the contents of a `Procfile`.

I based this patch on [launchys JRuby support](https://github.com/copiousfreetime/launchy/pull/10) and I too confirmed these changes did not break 1.8.7, 1.9.2 or 1.9.3.
2012-01-22 16:58:30 -05:00
jc00ke
853a88dfbf Move spoon dep to Gemfile
By moving it to the Gemfile & using platform we're able avoid installing
spoon for other Ruby implementations.
2012-01-22 13:43:37 -08:00
jc00ke
b4cab08327 Using spoon for JRuby support 2012-01-22 12:27:25 -08:00
brainopia
ade0005a92 Add specs for Foreman::Process#run 2012-01-22 22:07:53 +04:00
brainopia
241b91a0d5 Implement Foreman::Process#kill,detach,alive?,dead? 2012-01-22 22:07:01 +04:00
brainopia
047f106d48 - Use explicit fakefs tag in specs
- Clean up trailing whitespace
2012-01-22 15:00:43 +04:00
brainopia
df043e60d8 Simplify Foreman::Process#with_environment 2012-01-22 08:57:09 +04:00
brainopia
158c184f8c Add specs for options of Foreman::Process#run 2012-01-22 08:55:45 +04:00
brainopia
2ed1fe8d44 Add specs for initialization of Foreman::Process 2012-01-22 08:54:42 +04:00
David Dollar
a008886bd0 use default bucket for storage 2012-01-20 18:21:55 -08:00
David Dollar
c62f892ff6 Merge pull request #138 from technomancy/debian
Debian
2012-01-20 18:07:45 -08:00
Phil Hagelberg
d885e019b3 Add Debian packaging. 2012-01-20 18:02:01 -08:00
Phil Hagelberg
cfd337b44d Ignore vendor dir. 2012-01-20 18:01:46 -08:00
Fletcher Nichol
e76f3533dc runit creates a full path to export directory. 2012-01-18 20:13:08 -07:00
David Dollar
1485eeb859 0.36.1 2012-01-18 11:18:39 -05:00
David Dollar
e0b5928e88 bump term-ansicolor in gemspec 2012-01-18 11:18:33 -05:00
David Dollar
a73dce5405 0.36.0 2012-01-17 22:21:36 -05:00
David Dollar
2abddb42b3 sync the writer stream 2012-01-17 22:21:16 -05:00
David Dollar
d961a32cfe capture stderr as well 2012-01-17 22:20:25 -05:00
David Dollar
2bfc065c1d update rake 2012-01-16 18:35:06 -05:00
David Dollar
fbe3d4ec69 0.35.0 2012-01-16 18:34:36 -05:00
David Dollar
631187e0d8 Merge pull request #132 from Viximo/feature/concurrency
Change default concurrency to 0 when concurrency is provided
2012-01-16 15:13:27 -08:00
Matt Griffin
92d1a4d367 Fix export specs 2012-01-16 17:39:21 -05:00
Matt Griffin
f4123f4ae1 Merge branch 'master' of https://github.com/michaeldwan/foreman into feature/concurrency
Conflicts:
	spec/foreman/engine_spec.rb
	spec/foreman/export/bluepill_spec.rb
	spec/resources/export/bluepill/app.pill
2012-01-16 17:18:14 -05:00
David Dollar
d4c2332c59 0.34.1 2012-01-16 09:53:56 -05:00
David Dollar
e257fc89c1 fix missing start desc 2012-01-16 09:53:43 -05:00
David Dollar
a278755ae4 0.34.0 2012-01-16 09:42:07 -05:00
David Dollar
3367a060a7 update man page 2012-01-16 09:41:36 -05:00
David Dollar
ac7e0743ac update docs for -d 2012-01-16 09:39:54 -05:00
David Dollar
e574880814 Merge pull request #101 from ndbroadbent/foreman
---

I just discovered the LiveReload gem, and wanted to use foreman to help me set up my development environments.

I didnt want to check in my custom development Procfiles, so I needed to alter the behaviour of:

> [The Procfiles] containing directory will be assumed to be the root directory of the application.

Ive set up some shared `Procfiles` for development, such as `Rails3Dev`, `Rails31Dev`, `JekyllDev`, etc.

Then I set up a bash alias for each of these Procfiles, such as:

```bash
alias rd31="foreman start -d . -f ~/dev/procfiles/Rails31Dev"
```

The only thing missing was the `-d` flag.

My `Rails31Dev` file looks like this:

```yaml
compass: compass watch --sass-dir app/assets/stylesheets --css-dir public/assets
livereload: livereload
passenger: passenger start
```

Thanks!

Conflicts:
	lib/foreman/cli.rb
2012-01-16 09:38:25 -05:00
Craig R Webster
7132cacbf6 Wrap around to the first colour when all the colours are used 2012-01-16 09:35:51 -05:00
David Dollar
c1f279aa6f run specs in random order 2012-01-16 09:33:34 -05:00
David Dollar
34cfe9ef9d update rspec 2012-01-16 09:33:34 -05:00
David Dollar
79fc3b8029 pedantry 2012-01-16 09:33:34 -05:00
Matthijs Langenberg
91140638e1 Set executable bit on runit run scripts. 2012-01-16 09:33:34 -05:00
David Dollar
48cc60c30f Merge pull request #114 from gburt/master
add more colors
2012-01-16 06:21:28 -08:00
David Dollar
533139ea9f 0.33.1 2012-01-16 09:18:48 -05:00
David Dollar
86e2056a24 Merge pull request #129 from fnichol/resolve-home-template
Expand template path under user's home directory (foreman export).
2012-01-16 06:17:43 -08:00
Fletcher Nichol
ab29963ee4 Expand template path under user's home directory.
* File.join won't expand `~` into `ENV['HOME']`
  (http://ruby-doc.org/core-1.9.3/File.html#method-c-expand_path)
* The FakeFS File.exists? implementation calls FileSystem#find
  (https://github.com/defunkt/fakefs/blob/master/lib/fakefs/file_system.rb#L22-33)
  containing a call to FileSystem#normalize_path which expands the
  path variable passed in
  (https://github.com/defunkt/fakefs/blob/master/lib/fakefs/file_system.rb#L91-98)
* The file system mocking library sets up a false expectation that `~`
  will be expanded in the #export_template method and consequently the
  production code can't use the template directory
* To guard against future regressions such as fixes/updates to FakeFS or
  using an alternate file system mocking library, the specs were updated
  to explicitly set `ENV['HOME']`
2012-01-15 19:09:52 -07:00
David Dollar
cf269c39da 0.33.0 2012-01-15 13:00:45 -05:00
David Dollar
76cd2e794b Revert "Merge pull request #125 from brainopia/master"
It appears that this is causing issues with process termination.

This reverts commit d2c9ce0f34, reversing
changes made to 98337c92e1.
2012-01-15 12:59:47 -05:00
David Dollar
83748cb538 0.32.0 2012-01-12 15:25:43 -08:00
David Dollar
d2c9ce0f34 Merge pull request #125 from brainopia/master
Support for complex cmds in Procfile
2012-01-12 15:23:15 -08:00
David Dollar
98337c92e1 Merge pull request #121 from Viximo/feature/run
Add "exec" action to allow execution of commands within the app environment
2012-01-09 16:02:42 -08:00
Matt Griffin
33d738b3f8 Return some whitespace that was accidentally removed 2012-01-09 17:15:20 -05:00
Matt Griffin
9432989fbe Steal the run method back from Thor so that it can be used in place for exec for running commands in the foreman environment.
Fix some error reporting.
2012-01-09 17:11:32 -05:00
brainopia
66b1483a75 Remove old cruft 2012-01-08 10:18:48 +07:00
brainopia
64bd4db128 In case someone wants to use bin/runner directly 2012-01-08 10:15:23 +07:00
brainopia
b561555f3a Fix for double fork 2012-01-08 09:42:51 +07:00
brainopia
baa7b7685c Use ruby exec which works with escaped cmd and replaces shell 2012-01-07 20:19:57 +07:00
brainopia
cfa6e6f259 Fix foreman to work with cmds containing pipes and redirects 2012-01-07 18:19:54 +07:00
Matt Griffin
a34bc59721 Add "exec" action to allow execution of arbitrary commands with the app's environment. 2012-01-04 15:22:10 -05:00
David Dollar
07e8ca4a4b tweak readme 2012-01-04 12:36:34 -05:00
David Dollar
342d30bbb8 0.31.0 2012-01-04 12:16:51 -05:00
David Dollar
268dd6240e make fork more robust 2012-01-04 12:15:55 -05:00
David Dollar
9e60b3e1a4 remove unnecessary debug 2012-01-04 12:15:38 -05:00
David Dollar
1c6285f8af add more information when shutting down 2012-01-04 12:15:17 -05:00
Gabriel Burt
5de1bd18ac add more colors 2011-12-30 13:55:46 -06:00
David Dollar
fff15bc627 Merge pull request #110 from lstoll/master
Different port range for each process type on 'foreman start'
2011-12-24 22:47:36 -08:00
Lincoln Stoll
a66157d611 Use different port ranges for each process type 2011-12-25 15:46:21 +11:00
David Dollar
fcfa913fb0 0.30.1 2011-12-23 08:48:34 -05:00
David Dollar
fc438472f9 require thread for mutex 2011-12-23 08:48:17 -05:00
David Dollar
fc95936327 0.30.0 2011-12-22 16:34:02 -05:00
David Dollar
0c27f78d46 compatibility with ruby 1.8 2011-12-22 16:33:49 -05:00
David Dollar
356c61f471 0.29.0 2011-12-22 01:03:11 -05:00
David Dollar
dcff4da220 0.28.0.pre2 2011-12-08 17:59:40 -08:00
David Dollar
888520ee99 fix pipe error 2011-12-08 17:59:27 -08:00
David Dollar
c7b6b334fd 0.28.0.pre1 2011-12-08 17:54:19 -08:00
David Dollar
f476920a05 Merge branch 'fork' 2011-12-08 17:53:50 -08:00
David Dollar
5436b68cf1 wip 2011-12-08 17:53:13 -08:00
David Dollar
c9411cd2b1 wip 2011-12-08 17:18:21 -08:00
David Dollar
6e95d1ce94 wip 2011-12-08 16:57:33 -08:00
David Dollar
c5548a345e wip 2011-12-08 16:19:19 -08:00
David Dollar
f668b87660 wip 2011-12-08 14:04:29 -08:00
David Dollar
914a1ee958 0.27.0 2011-12-05 15:46:40 -05:00
David Dollar
e1c2946718 add changelog 2011-12-05 15:46:22 -05:00
David Dollar
6160246da0 Merge pull request #103 from csquared/load_env_from_irb
Load env from irb
2011-12-05 12:37:05 -08:00
Chris Continanza
2e8030dbd4 refactor load_env to apply_environment 2011-12-05 12:36:23 -08:00
Chris Continanza
58e4cdafd7 rename load! to load_env! 2011-12-05 12:27:32 -08:00
Chris Continanza
44a5dff724 use ./.env as default 2011-12-05 11:37:51 -08:00
Chris Continanza
e33921f083 load contents from env file 2011-12-03 15:43:51 -08:00
Chris Continanza
79cf541ebe refactor engine to expose env methods 2011-12-03 15:43:07 -08:00
Nathan Broadbent
8bc8cb4b2e Added option to specify app_root, if executing a Procfile from a shared location 2011-12-03 15:16:06 +08:00
David Dollar
39ace26ae1 disable email notifications 2011-11-14 12:23:50 -05:00
David Dollar
c383359136 add travis config 2011-11-11 23:16:59 -05:00
Michael Dwan
33aa1efc90 default process concurrency is 0 when concurrency options specified, otherwise default concurrency is 1 2011-11-07 13:10:18 -07:00
64 changed files with 1684 additions and 548 deletions

1
.env
View File

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

3
.gitignore vendored
View File

@@ -1,8 +1,9 @@
/.bundle
/.rbenv-version
/coverage
/example/log/*
/man/*.html
/man/*.markdown
/pkg
/tags
/vendor

14
.travis.yml Normal file
View File

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

160
Changelog.md Normal file
View File

@@ -0,0 +1,160 @@
## 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]

15
Gemfile
View File

@@ -2,14 +2,21 @@ source "http://rubygems.org"
gemspec
platform :mingw do
gem "win32console", "~> 1.3.0"
end
platform :jruby do
gem "posix-spawn", "~> 0.3.6"
end
group :development do
gem 'aws-s3'
gem 'parka'
gem 'rake'
gem 'ronn'
gem 'fakefs', '~> 0.3.2'
gem 'rcov', '~> 0.9.8'
gem 'rr', '~> 1.0.2'
gem 'rspec', '~> 2.6.0'
gem 'aws-s3'
gem "rubyzip"
gem 'rspec', '~> 2.0'
gem "simplecov", :require => false
end

View File

@@ -1,8 +1,8 @@
PATH
remote: .
specs:
foreman (0.26.1)
term-ansicolor (~> 1.0.5)
foreman (0.39.0)
term-ansicolor (~> 1.0.7)
thor (>= 0.13.6)
GEM
@@ -14,17 +14,19 @@ GEM
xml-simple
builder (3.0.0)
crack (0.1.8)
diff-lcs (1.1.2)
diff-lcs (1.1.3)
fakefs (0.3.2)
hpricot (0.8.2)
hpricot (0.8.6)
hpricot (0.8.6-java)
mime-types (1.16)
multi_json (1.0.4)
mustache (0.11.2)
parka (0.6.2)
crack
rest-client
thor
rake (0.9.2)
rcov (0.9.8)
posix-spawn (0.3.6)
rake (0.9.2.2)
rdiscount (1.6.5)
rest-client (1.6.1)
mime-types (>= 1.16)
@@ -33,30 +35,37 @@ GEM
mustache (>= 0.7.0)
rdiscount (>= 1.5.8)
rr (1.0.2)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
rspec (2.8.0)
rspec-core (~> 2.8.0)
rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.8.0)
rspec-core (2.8.0)
rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
rubyzip (0.9.4)
term-ansicolor (1.0.6)
rspec-mocks (2.8.0)
simplecov (0.5.4)
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
term-ansicolor (1.0.7)
thor (0.14.6)
win32console (1.3.0-x86-mingw32)
xml-simple (1.0.15)
PLATFORMS
java
ruby
x86-mingw32
DEPENDENCIES
aws-s3
fakefs (~> 0.3.2)
foreman!
parka
posix-spawn (~> 0.3.6)
rake
rcov (~> 0.9.8)
ronn
rr (~> 1.0.2)
rspec (~> 2.6.0)
rubyzip
rspec (~> 2.0)
simplecov
win32console (~> 1.3.0)

View File

@@ -1,49 +0,0 @@
# Foreman
## Installation
* Rubygems
gem install foreman
* OSX
http://assets.foreman.io/foreman/foreman.pkg
* Standalone Tarball
http://assets.foreman.io/foreman/foreman.tgz
## Description
http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
## Manual
See the [man page](http://ddollar.github.com/foreman) for usage.
## Authorship
Created by David Dollar
Patches contributed by:
* Adam Wiggins
* Dan Peterson
* Hunter Nield
* Jay Zeschin
* Keith Rarick
* Khaja Minhajuddin
* Matt Haynes
* Michael van Rooijen
* Mike Javorski
* Nathan L Smith
* Nick Zadrozny
* Ricardo Chimal, Jr
* Thom May
* clifff
* Greg Reinacker
## License
MIT

40
README.md Normal file
View File

@@ -0,0 +1,40 @@
# 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)
## Authors
#### Created and maintained by
David Dollar
#### Patches contributed by
Adam Wiggins, Chris Continanza, Chris Lowder, Craig R Webster, Dan Farina, Dan Peterson, David Dollar, Fletcher Nichol, Florian Apolloner, Gabriel Burt, Gamaliel Toro, Greg Reinacker, Hugues Le Gendre, Hunter Nield, Iain Hecker, Jay Zeschin, John Firebaugh, Keith Rarick, Khaja Minhajuddin, Lincoln Stoll, Marcos Muino Garcia, Mark McGranaghan, Matt Griffin, Matt Haynes, Matthijs Langenberg, Michael Dwan, Michael van Rooijen, Mike Javorski, Nathan Broadbent, Nathan L Smith, Nick Zadrozny, Phil Hagelberg, Ricardo Chimal, Jr, Thom May, Tom Ward, brainopia, clifff, jc00ke
## License
MIT

166
Rakefile
View File

@@ -1,168 +1,8 @@
require "rubygems"
require "bundler"
Bundler.setup
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", ".bundle", "--exclude", "spec" ]
end
desc 'Build the manual'
task :man do
ENV['RONN_MANUAL'] = "Foreman Manual"
ENV['RONN_ORGANIZATION'] = "Foreman #{Foreman::VERSION}"
sh "ronn -w -s toc -r5 --markdown man/*.ronn"
end
desc "Commit the manual to git"
task "man:commit" => :man do
sh "git add README.markdown"
sh "git commit -m 'update readme' || echo 'nothing to commit'"
end
desc "Generate the Github docs"
task :pages => "man:commit" do
sh %{
cp man/foreman.1.html /tmp/foreman.1.html
git checkout gh-pages
rm ./index.html
cp /tmp/foreman.1.html ./index.html
git add -u index.html
git commit -m "saving man page to github docs"
git push origin -f gh-pages
git checkout master
}
end
## dist
require "erb"
require "fileutils"
require "tmpdir"
def assemble(source, target, perms=0644)
FileUtils.mkdir_p(File.dirname(target))
File.open(target, "w") do |f|
f.puts ERB.new(File.read(source)).result(binding)
end
File.chmod(perms, target)
end
def assemble_distribution(target_dir=Dir.pwd)
distribution_files.each do |source|
target = source.gsub(/^#{project_root}/, target_dir)
FileUtils.mkdir_p(File.dirname(target))
FileUtils.cp(source, target)
end
end
GEM_BLACKLIST = %w( bundler foreman )
def assemble_gems(target_dir=Dir.pwd)
lines = %x{ bundle show }.strip.split("\n")
raise "error running bundler" unless $?.success?
%x{ env BUNDLE_WITHOUT="development:test" bundle show }.split("\n").each do |line|
if line =~ /^ \* (.*?) \((.*?)\)/
next if GEM_BLACKLIST.include?($1)
puts "vendoring: #{$1}-#{$2}"
gem_dir = %x{ bundle show #{$1} }.strip
FileUtils.mkdir_p "#{target_dir}/vendor/gems"
%x{ cp -R "#{gem_dir}" "#{target_dir}/vendor/gems" }
end
end.compact
end
def beta?
Foreman::VERSION.to_s =~ /pre/
end
def clean(file)
rm file if File.exists?(file)
end
def distribution_files
require "foreman/distribution"
Foreman::Distribution.files
end
def mkchdir(dir)
FileUtils.mkdir_p(dir)
Dir.chdir(dir) do |dir|
yield(File.expand_path(dir))
end
end
def pkg(filename)
File.expand_path("../pkg/#{filename}", __FILE__)
end
def project_root
File.dirname(__FILE__)
end
def resource(name)
File.expand_path("../dist/resources/#{name}", __FILE__)
end
def s3_connect
return if @s3_connected
require "aws/s3"
unless ENV["DAVID_RELEASE_ACCESS"] && ENV["DAVID_RELEASE_SECRET"]
puts "please set DAVID_RELEASE_ACCESS and DAVID_RELEASE_SECRET in your environment"
exit 1
end
AWS::S3::Base.establish_connection!(
:access_key_id => ENV["DAVID_RELEASE_ACCESS"],
:secret_access_key => ENV["DAVID_RELEASE_SECRET"]
)
@s3_connected = true
end
def store(package_file, filename, bucket="assets.foreman.io")
s3_connect
puts "storing: #{filename}"
AWS::S3::S3Object.store(filename, File.open(package_file), bucket, :access => :public_read)
end
def tempdir
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
yield(dir)
end
end
end
def version
require "foreman/version"
Foreman::VERSION
end
Dir[File.expand_path("../dist/**/*.rake", __FILE__)].each do |rake|
import rake
Dir[File.expand_path("../tasks/*.rake", __FILE__)].each do |task|
load task
end

View File

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

36
bin/foreman-runner Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/sh
#
#/ Usage: foreman-runner [-d <dir>] <command>
#/
#/ Run a command with exec, optionally changing directory first
set -e
error() {
echo $@ >&2
exit 1
}
usage() {
cat $0 | grep '^#/' | cut -c4-
exit
}
while getopts ":hd:" OPT; do
case $OPT in
d) cd "$OPTARG" ;;
h) usage ;;
\?) error "invalid option: -$OPTARG" ;;
:) error "option -$OPTARG requires an argument" ;;
esac
done
shift $((OPTIND-1))
command=$1
if [ -z "$1" ]; then
usage
fi
exec $1

View File

@@ -1,2 +1,3 @@
ticker: ruby ./ticker $PORT
error: ruby ./error
utf8: ruby ./utf8

11
data/example/utf8 Executable file
View 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

View File

@@ -3,9 +3,9 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
app.uid = "<%= user %>"
app.gid = "<%= user %>"
<% engine.processes.each do |process| %>
<% engine.procfile.entries.each do |process| %>
<% 1.upto(concurrency[process.name]) do |num| %>
<% port = engine.port_for(process, num, options[:port]) %>
<% port = engine.port_for(process, num, self.port) %>
app.process("<%= process.name %>-<%=num%>") do |process|
process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
@@ -19,7 +19,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "<%= app %>-<%= process.name %>"
end
<% end %>

33
dist/deb.rake vendored Normal file
View 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

16
dist/jruby.rake vendored Normal file
View 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 "parka push -f #{pkg("foreman-#{version}-jruby.gem")}"
end

16
dist/mingw32.rake vendored Normal file
View 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 "parka push -f #{pkg("foreman-#{version}-mingw32.gem")}"
end

15
dist/pkg.rake vendored
View File

@@ -13,7 +13,7 @@ file pkg("foreman-#{version}.pkg") => distribution_files do |t|
mkdir_p "pkg"
mkdir_p "pkg/Resources"
mkdir_p "pkg/foreman-#{version}.pkg"
mkdir_p "pkg/foreman.pkg"
dist = File.read(resource("pkg/Distribution.erb"))
dist = ERB.new(dist).result(binding)
@@ -21,20 +21,21 @@ file pkg("foreman-#{version}.pkg") => distribution_files do |t|
dist = File.read(resource("pkg/PackageInfo.erb"))
dist = ERB.new(dist).result(binding)
File.open("pkg/foreman-#{version}.pkg/PackageInfo", "w") { |f| f.puts dist }
File.open("pkg/foreman.pkg/PackageInfo", "w") { |f| f.puts dist }
mkdir_p "pkg/foreman-#{version}.pkg/Scripts"
cp resource("pkg/postinstall"), "pkg/foreman-#{version}.pkg/Scripts/postinstall"
chmod 0755, "pkg/foreman-#{version}.pkg/Scripts/postinstall"
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-#{version}.pkg/Bom }
sh %{ mkbom -s foreman pkg/foreman.pkg/Bom }
Dir.chdir("foreman") do
sh %{ pax -wz -x cpio . > ../pkg/foreman-#{version}.pkg/Payload }
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

12
dist/resources/deb/control vendored Normal file
View 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
View 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

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

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

View File

@@ -10,14 +10,14 @@
]]></script>
<choices-outline>
<line choice="git"/>
<line choice="foreman-<%= version %>"/>
<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-<%= version %>" title="foreman">
<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-<%= version %>.pkg</pkg-ref>
<pkg-ref id="io.foreman.installer" installKBytes="<%= kbytes %>" version="<%= version %>" auth="Root">#foreman.pkg</pkg-ref>
</installer-script>

View File

@@ -4,4 +4,3 @@
<postinstall file="./postinstall"/>
</scripts>
</pkg-info>

2
dist/tgz.rake vendored
View File

@@ -3,7 +3,7 @@ file pkg("foreman-#{version}.tgz") => distribution_files do |t|
mkchdir("foreman") do
assemble_distribution
assemble_gems
rm_rf "bin"
rm_f "bin/foreman"
assemble resource("tgz/foreman"), "foreman", 0755
end

View File

@@ -16,6 +16,16 @@ Gem::Specification.new do |gem|
gem.files = Dir["**/*"].select { |d| d =~ %r{^(README|bin/|data/|ext/|lib/|spec/|test/)} }
gem.files << "man/foreman.1"
gem.add_dependency 'term-ansicolor', '~> 1.0.5'
gem.add_dependency 'term-ansicolor', '~> 1.0.7'
gem.add_dependency 'thor', '>= 0.13.6'
if ENV["PLATFORM"] == "java"
gem.add_dependency "posix-spawn", "~> 0.3.6"
gem.platform = Gem::Platform.new("java")
end
if ENV["PLATFORM"] == "mingw32"
gem.add_dependency "win32console", "~> 1.3.0"
gem.platform = Gem::Platform.new("mingw32")
end
end

View File

@@ -4,5 +4,22 @@ module Foreman
class AppDoesNotExist < Exception; end
end
# load contents of env_file into ENV
def self.load_env!(env_file = './.env')
require 'foreman/engine'
Foreman::Engine.load_env!(env_file)
end
def self.runner
File.expand_path("../../bin/foreman-runner", __FILE__)
end
def self.jruby?
defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java"
end
def self.windows?
defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
end
end

View File

@@ -1,27 +1,36 @@
require "foreman"
require "foreman/helpers"
require "foreman/engine"
require "foreman/export"
require "thor"
require "yaml"
class Foreman::CLI < Thor
include Foreman::Helpers
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
desc "start [PROCESS]", "Start the application, or a specific process"
desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :port, :type => :numeric, :aliases => "-p"
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
class << self
# Hackery. Take the run method away from Thor so that we can redefine it.
def is_thor_reserved_word?(word, type)
return false if word == 'run'
super
end
end
def start(process=nil)
check_procfile!
if process
engine.execute(process)
else
engine.start
end
engine.options[:concurrency] = "#{process}=1" if process
engine.start
end
desc "export FORMAT LOCATION", "Export the application to another process management format"
@@ -32,22 +41,12 @@ class Foreman::CLI < Thor
method_option :port, :type => :numeric, :aliases => "-p"
method_option :user, :type => :string, :aliases => "-u"
method_option :template, :type => :string, :aliases => "-t"
method_option :concurrency, :type => :string, :aliases => "-c",
:banner => '"alpha=5,bar=3"'
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
def export(format, location=nil)
check_procfile!
formatter = case format
when "inittab" then Foreman::Export::Inittab
when "upstart" then Foreman::Export::Upstart
when "bluepill" then Foreman::Export::Bluepill
when "runit" then Foreman::Export::Runit
else error "Unknown export format: #{format}."
end
formatter.new(engine).export(location, options)
formatter = Foreman::Export.formatter(format)
formatter.new(location, engine, options).export
rescue Foreman::Export::Exception => ex
error ex.message
end
@@ -55,8 +54,21 @@ class Foreman::CLI < Thor
desc "check", "Validate your application's Procfile"
def check
error "no processes defined" unless engine.processes.length > 0
display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})"
error "no processes defined" unless engine.procfile.entries.length > 0
puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
end
desc "run COMMAND", "Run a command using your application's environment"
def run(*args)
engine.apply_environment!
begin
exec args.join(" ")
rescue Errno::EACCES
error "not executable: #{args.first}"
rescue Errno::ENOENT
error "command not found: #{args.first}"
end
end
private ######################################################################
@@ -73,24 +85,15 @@ private ######################################################################
options[:procfile] || "Procfile"
end
def display(message)
puts message
end
def error(message)
puts "ERROR: #{message}"
exit 1
end
def procfile_exists?(procfile)
File.exist?(procfile)
end
def options
original_options = super
return original_options unless File.exists?(".foreman")
defaults = YAML::load_file(".foreman") || {}
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
end
end

View File

@@ -2,60 +2,50 @@ require "foreman"
require "foreman/process"
require "foreman/procfile"
require "foreman/utils"
require "pty"
require "tempfile"
require "timeout"
require "term/ansicolor"
require "fileutils"
require "thread"
class Foreman::Engine
attr_reader :procfile
attr_reader :directory
attr_reader :environment
attr_reader :options
extend Term::ANSIColor
COLORS = [ cyan, yellow, green, magenta, red ]
COLORS = [ cyan, yellow, green, magenta, red, blue,
intense_cyan, intense_yellow, intense_green, intense_magenta,
intense_red, intense_blue ]
def initialize(procfile, options={})
@procfile = Foreman::Procfile.new(procfile)
@directory = File.expand_path(File.dirname(procfile))
@options = options
@directory = options[:app_root] || File.expand_path(File.dirname(procfile))
@options = options.dup
@environment = read_environment_files(options[:env])
@output_mutex = Mutex.new
end
def self.load_env!(env_file)
@environment = read_environment_files(env_file)
apply_environment!
end
def start
proctitle "ruby: foreman master"
termtitle "#{File.basename(@directory)} - foreman (#{processes.size} processes)"
processes.each do |process|
process.color = next_color
fork process
end
termtitle "#{File.basename(@directory)} - foreman"
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully }
assign_colors
spawn_processes
watch_for_output
watch_for_termination
end
def execute(name)
error "no such process: #{name}" unless process = procfile[name]
process.color = next_color
fork process
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully }
watch_for_termination
end
def processes
procfile.processes
end
def port_for(process, num, base_port=nil)
base_port ||= 5000
offset = procfile.process_names.index(process.name) * 100
@@ -64,84 +54,99 @@ class Foreman::Engine
private ######################################################################
def fork(process)
def spawn_processes
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
1.upto(concurrency[process.name]) do |num|
fork_individual(process, num, port_for(process, num, @options[:port]))
procfile.entries.each do |entry|
reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY"))
entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
running_processes[process.pid] = process
readers[process] = reader
end
end
end
def fork_individual(process, num, port)
@environment.each { |k,v| ENV[k] = v }
ENV["PORT"] = port.to_s
ENV["PS"] = "#{process.name}.#{num}"
pid = Process.fork do
run(process)
end
info "started with pid #{pid}", process
running_processes[pid] = process
end
def run(process)
proctitle "ruby: foreman #{process.name}"
trap("SIGINT", "IGNORE")
begin
Dir.chdir directory do
PTY.spawn(process.command) do |stdin, stdout, pid|
trap("SIGTERM") { Process.kill("SIGTERM", pid) }
until stdin.eof?
info stdin.gets, process
end
end
end
rescue PTY::ChildExited, Interrupt, Errno::EIO, Errno::ENOENT
begin
info "process exiting", process
rescue Interrupt
end
end
def base_port
options[:port] || 5000
end
def kill_all(signal="SIGTERM")
running_processes.each do |pid, process|
Process.kill(signal, pid) rescue Errno::ESRCH
info "sending #{signal} to pid #{pid}"
process.kill signal
end
end
def terminate_gracefully
return if @terminating
@terminating = true
info "sending SIGTERM to all processes"
kill_all "SIGTERM"
Timeout.timeout(3) { Process.waitall }
Timeout.timeout(5) do
while running_processes.length > 0
pid, status = Process.wait2
process = running_processes.delete(pid)
info "process terminated", process.name
end
end
rescue Timeout::Error
info "sending SIGKILL to all processes"
kill_all "SIGKILL"
end
def poll_readers
rs, ws = IO.select(readers.values, [], [], 1)
(rs || []).each do |r|
data = r.gets
next unless data
data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
ps, message = data.split(",", 2)
color = colors[ps.split(".").first]
info message, ps, color
end
end
def watch_for_output
Thread.new do
require "win32console" if Foreman.windows?
begin
loop do
poll_readers
end
rescue Exception => ex
puts ex.message
puts ex.backtrace
end
end
end
def watch_for_termination
pid, status = Process.wait2
process = running_processes.delete(pid)
info "process terminated", process
info "process terminated", process.name
terminate_gracefully
kill_all
rescue Errno::ECHILD
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
def info(message, name="system", color=Term::ANSIColor.white)
output = ""
output += color
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
output += Term::ANSIColor.reset
output += message.chomp
puts output
end
def error(message)
puts "ERROR: #{message}"
exit 1
def print(message=nil)
@output_mutex.synchronize do
$stdout.print message
end
end
def puts(message=nil)
@output_mutex.synchronize do
$stdout.puts message
end
end
def longest_process_name
@@ -152,9 +157,8 @@ private ######################################################################
end
end
def pad_process_name(process)
name = process ? "#{ENV["PS"]}" : "system"
name.ljust(longest_process_name + 3) # add 3 for process number padding
def pad_process_name(name="system")
name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
end
def proctitle(title)
@@ -162,40 +166,74 @@ private ######################################################################
end
def termtitle(title)
printf("\033]0;#{title}\007")
printf("\033]0;#{title}\007") unless Foreman.windows?
end
def running_processes
@running_processes ||= {}
end
def readers
@readers ||= {}
end
def colors
@colors ||= {}
end
def assign_colors
procfile.entries.each do |entry|
colors[entry.name] = next_color
end
end
def process_by_reader(reader)
readers.invert[reader]
end
def next_color
@current_color ||= -1
@current_color += 1
@current_color >= COLORS.length ? "" : COLORS[@current_color]
@current_color = 0 if COLORS.length < @current_color
COLORS[@current_color]
end
def read_environment_files(filenames)
environment = {}
module Env
attr_reader :environment
(filenames || "").split(",").map(&:strip).each do |filename|
error "No such file: #{filename}" unless File.exists?(filename)
environment.merge!(read_environment(filename))
end
def read_environment_files(filenames)
environment = {}
environment.merge!(read_environment(".env")) unless filenames
environment
end
def read_environment(filename)
return {} unless File.exists?(filename)
File.read(filename).split("\n").inject({}) do |hash, line|
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
hash[$1] = $2
(filenames || "").split(",").map(&:strip).each do |filename|
error "No such file: #{filename}" unless File.exists?(filename)
environment.merge!(read_environment(filename))
end
hash
environment.merge!(read_environment(".env")) unless filenames
environment
end
def read_environment(filename)
return {} unless File.exists?(filename)
File.read(filename).split("\n").inject({}) do |hash, line|
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
hash[$1] = $2
end
hash
end
end
def apply_environment!
@environment.each { |k,v| ENV[k] = v }
end
def error(message)
puts "ERROR: #{message}"
exit 1
end
end
include Env
extend Env
end

View File

@@ -1,9 +1,30 @@
require "foreman"
require "foreman/helpers"
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"

View File

@@ -3,10 +3,17 @@ require "foreman/utils"
class Foreman::Export::Base
attr_reader :engine
attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
def initialize(engine)
@engine = engine
def initialize(location, engine, options={})
@location = location
@engine = engine
@app = options[:app]
@log = options[:log]
@port = options[:port]
@user = options[:user]
@template = options[:template]
@concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
end
def export
@@ -26,7 +33,7 @@ private ######################################################################
def export_template(exporter, file, template_root)
if template_root && File.exist?(file_path = File.join(template_root, file))
File.read(file_path)
elsif File.exist?(file_path = File.join("~/.foreman/templates", file))
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__))

View File

@@ -3,23 +3,21 @@ require "foreman/export"
class Foreman::Export::Bluepill < Foreman::Export::Base
def export(location, options={})
def export
error("Must specify a location") unless location
FileUtils.mkdir_p location
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template]
app = self.app || File.basename(engine.directory)
user = self.user || app
log_root = self.log || "/var/log/#{app}"
template_root = self.template
Dir["#{location}/#{app}.pill"].each do |file|
say "cleaning up: #{file}"
FileUtils.rm(file)
end
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
master_template = export_template("bluepill", "master.pill.erb", template_root)
master_config = ERB.new(master_template).result(binding)
write_file "#{location}/#{app}.pill", master_config

View File

@@ -2,20 +2,18 @@ require "foreman/export"
class Foreman::Export::Inittab < Foreman::Export::Base
def export(fname=nil, options={})
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
def export
app = self.app || File.basename(engine.directory)
user = self.user || app
log_root = self.log || "/var/log/#{app}"
inittab = []
inittab << "# ----- foreman #{app} processes -----"
engine.processes.inject(1) do |index, process|
1.upto(concurrency[process.name]) do |num|
engine.procfile.entries.inject(1) do |index, process|
1.upto(self.concurrency[process.name]) do |num|
id = app.slice(0, 2).upcase + sprintf("%02d", index)
port = engine.port_for(process, num, options[:port])
port = engine.port_for(process, num, self.port)
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
index += 1
end
@@ -26,12 +24,12 @@ class Foreman::Export::Inittab < Foreman::Export::Base
inittab = inittab.join("\n") + "\n"
if fname
if location == "-"
puts inittab
else
FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}"
FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}"
write_file(fname, inittab)
else
puts inittab
write_file(location, inittab)
end
end

View File

@@ -3,58 +3,57 @@ require "foreman/export"
class Foreman::Export::Runit < Foreman::Export::Base
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
def export(location, options={})
def export
error("Must specify a location") unless location
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template]
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
app = self.app || File.basename(engine.directory)
user = self.user || app
log_root = self.log || "/var/log/#{app}"
template_root = self.template
run_template = export_template('runit', 'run.erb', template_root)
log_run_template = export_template('runit', 'log_run.erb', template_root)
engine.processes.each do |process|
1.upto(concurrency[process.name]) do |num|
engine.procfile.entries.each do |process|
1.upto(self.concurrency[process.name]) do |num|
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
process_env_directory = "#{process_directory}/env"
process_log_directory = "#{process_directory}/log"
create_directory process_directory
create_directory process_env_directory
create_directory process_log_directory
run = ERB.new(run_template).result(binding)
write_file "#{process_directory}/run", run
port = engine.port_for(process, num, options[:port])
FileUtils.chmod 0755, "#{process_directory}/run"
port = engine.port_for(process, num, self.port)
environment_variables = {'PORT' => port}.
merge(engine.environment).
merge(inline_variables(process.command))
environment_variables.each_pair do |var, env|
write_file "#{process_env_directory}/#{var.upcase}", env
end
log_run = ERB.new(log_run_template).result(binding)
write_file "#{process_log_directory}/run", log_run
FileUtils.chmod 0755, "#{process_log_directory}/run"
end
end
end
private
def create_directory(location)
say "creating: #{location}"
FileUtils.mkdir(location)
FileUtils.mkdir_p(location)
end
def inline_variables(command)
variable_name_regex =
variable_name_regex =
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
end
end
end

View File

@@ -3,37 +3,35 @@ require "foreman/export"
class Foreman::Export::Upstart < Foreman::Export::Base
def export(location, options={})
def export
error("Must specify a location") unless location
FileUtils.mkdir_p location
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template]
app = self.app || File.basename(engine.directory)
user = self.user || app
log_root = self.log || "/var/log/#{app}"
template_root = self.template
Dir["#{location}/#{app}*.conf"].each do |file|
say "cleaning up: #{file}"
FileUtils.rm(file)
end
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
master_template = export_template("upstart", "master.conf.erb", template_root)
master_config = ERB.new(master_template).result(binding)
write_file "#{location}/#{app}.conf", master_config
process_template = export_template("upstart", "process.conf.erb", template_root)
engine.processes.each do |process|
next if (conc = concurrency[process.name]) < 1
engine.procfile.entries.each do |process|
next if (conc = self.concurrency[process.name]) < 1
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
process_master_config = ERB.new(process_master_template).result(binding)
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
1.upto(concurrency[process.name]) do |num|
port = engine.port_for(process, num, options[:port])
1.upto(self.concurrency[process.name]) do |num|
port = engine.port_for(process, num, self.port)
process_config = ERB.new(process_template).result(binding)
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
end

45
lib/foreman/helpers.rb Normal file
View 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

View File

@@ -1,14 +1,96 @@
require "foreman"
require "rubygems"
class Foreman::Process
attr_reader :name
attr_reader :command
attr_accessor :color
attr_reader :entry
attr_reader :num
attr_reader :pid
attr_reader :port
def initialize(name, command)
@name = name
@command = command
def initialize(entry, num, port)
@entry = entry
@num = num
@port = port
end
def run(pipe, basedir, environment)
with_environment(environment.merge("PORT" => port.to_s)) do
run_process basedir, entry.command, pipe
end
end
def name
"%s.%s" % [ entry.name, num ]
end
def kill(signal)
pid && Process.kill(signal, pid)
rescue Errno::ESRCH
false
end
def detach
pid && Process.detach(pid)
end
def alive?
kill(0)
end
def dead?
!alive?
end
private
def fork_with_io(command, basedir)
reader, writer = IO.pipe
command = replace_command_env(command)
pid = if Foreman.windows?
Dir.chdir(basedir) do
Process.spawn command, :out => writer, :err => writer
end
elsif Foreman.jruby?
require "posix/spawn"
POSIX::Spawn.spawn(Foreman.runner, "-d", basedir, command, {
:out => writer, :err => writer
})
else
fork do
writer.sync = true
$stdout.reopen writer
$stderr.reopen writer
reader.close
exec Foreman.runner, "-d", basedir, command
end
end
[ reader, pid ]
end
def run_process(basedir, command, pipe)
io, @pid = fork_with_io(command, basedir)
output pipe, "started with pid %d" % @pid
Thread.new do
until io.eof?
output pipe, io.gets
end
end
end
def output(pipe, message)
pipe.puts "%s,%s" % [ name, message ]
end
def replace_command_env(command)
command.gsub(/\$(\w+)/) { |e| ENV[e[1..-1]] }
end
def with_environment(environment)
original = ENV.to_hash
ENV.update environment
yield
ensure
ENV.replace original
end
end

View File

@@ -1,4 +1,5 @@
require "foreman"
require "foreman/procfile_entry"
# A valid Procfile entry is captured by this regex.
# All other lines are ignored.
@@ -10,18 +11,18 @@ require "foreman"
#
class Foreman::Procfile
attr_reader :processes
attr_reader :entries
def initialize(filename)
@processes = parse_procfile(filename)
end
def process_names
processes.map(&:name)
@entries = parse_procfile(filename)
end
def [](name)
processes.detect { |process| process.name == name }
entries.detect { |entry| entry.name == name }
end
def process_names
entries.map(&:name)
end
private
@@ -29,7 +30,7 @@ private
def parse_procfile(filename)
File.read(filename).split("\n").map do |line|
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
Foreman::Process.new($1, $2)
Foreman::ProcfileEntry.new($1, $2)
end
end.compact
end

View File

@@ -0,0 +1,22 @@
require "foreman"
class Foreman::ProcfileEntry
attr_reader :name
attr_reader :command
attr_accessor :color
def initialize(name, command)
@name = name
@command = command
end
def spawn(num, pipe, basedir, environment, base_port)
(1..num).to_a.map do |n|
process = Foreman::Process.new(self, n, base_port + (n-1))
process.run(pipe, basedir, environment)
process
end
end
end

View File

@@ -5,7 +5,10 @@ class Foreman::Utils
def self.parse_concurrency(concurrency)
begin
pairs = concurrency.to_s.gsub(/\s/, "").split(",")
pairs.inject(Hash.new(1)) do |hash, pair|
default = concurrency.nil? ? 1 : 0
pairs.inject(Hash.new(default)) do |hash, pair|
process, amount = pair.split("=")
hash.update(process => amount.to_i)
end

View File

@@ -1,5 +1,5 @@
module Foreman
VERSION = "0.26.1"
VERSION = "0.39.0"
end

View File

@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "FOREMAN" "1" "November 2011" "Foreman 0.26.0" "Foreman Manual"
.TH "FOREMAN" "1" "January 2012" "Foreman 0.37.2" "Foreman Manual"
.
.SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications
@@ -68,6 +68,10 @@ Specify the user the application should be run as\. Defaults to the app name
These options control all modes of foreman\'s operation\.
.
.TP
\fB\-d\fR, \fB\-\-directory\fR
Specify an alternate application root\. This defaults to the directory containing the Procfile\.
.
.TP
\fB\-e\fR, \fB\-\-env\fR
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
.

View File

@@ -66,6 +66,10 @@ The following options control how the application is run:
These options control all modes of foreman's operation.
* `-d`, `--directory`:
Specify an alternate application root. This defaults to the directory
containing the Procfile.
* `-e`, `--env`:
Specify an alternate environment file. You can specify more than one
file by using: `--env file1,file2`.

View File

@@ -1,8 +1,10 @@
require "spec_helper"
require "foreman/cli"
describe "Foreman::CLI" do
describe "Foreman::CLI", :fakefs do
subject { Foreman::CLI.new }
let(:engine) { subject.send(:engine) }
let(:entries) { engine.procfile.entries.inject({}) { |h,e| h.update(e.name => e) } }
describe "start" do
describe "with a non-existent Procfile" do
@@ -22,15 +24,35 @@ describe "Foreman::CLI" do
mock.instance_of(Foreman::Engine).start
subject.start
end
it "can run a single process" do
dont_allow(subject).error
stub(engine).watch_for_output
stub(engine).watch_for_termination
mock(entries["alpha"]).spawn(1, is_a(IO), engine.directory, {}, 5000) { [] }
mock(entries["bravo"]).spawn(0, is_a(IO), engine.directory, {}, 5100) { [] }
subject.start("alpha")
end
end
end
describe "export" do
describe "options" do
it "uses .foreman" do
write_procfile
File.open(".foreman", "w") { |f| f.puts "concurrency: alpha=2" }
mock_export = mock(Foreman::Export::Upstart)
mock(Foreman::Export::Upstart).new("/upstart", is_a(Foreman::Engine), { "concurrency" => "alpha=2" }) { mock_export }
mock_export.export
foreman %{ export upstart /upstart }
end
it "respects --env" do
write_procfile
write_env("envfile")
mock.instance_of(Foreman::Export::Upstart).export("/upstart", { "env" => "envfile" })
mock_export = mock(Foreman::Export::Upstart)
mock(Foreman::Export::Upstart).new("/upstart", is_a(Foreman::Engine), { "env" => "envfile" }) { mock_export }
mock_export.export
foreman %{ export upstart /upstart --env envfile }
end
end
@@ -47,10 +69,18 @@ describe "Foreman::CLI" do
describe "with a Procfile" do
before(:each) { write_procfile }
describe "with an invalid formatter" do
describe "with a formatter with a generic error" do
before do
mock(Foreman::Export).formatter("errorful") { Class.new(Foreman::Export::Base) do
def export
raise Foreman::Export::Exception.new("foo")
end
end }
end
it "prints an error" do
mock_error(subject, "Unknown export format: invalidformatter.") do
subject.export("invalidformatter")
mock_error(subject, "foo") do
subject.export("errorful")
end
end
end
@@ -60,7 +90,9 @@ describe "Foreman::CLI" do
it "runs successfully" do
dont_allow(subject).error
mock.instance_of(Foreman::Export::Upstart).export("/tmp/foo", {})
mock_export = mock(Foreman::Export::Upstart)
mock(Foreman::Export::Upstart).new("/tmp/foo", is_a(Foreman::Engine), {}) { mock_export }
mock_export.export
subject.export("upstart", "/tmp/foo")
end
end
@@ -72,7 +104,7 @@ describe "Foreman::CLI" do
before { write_procfile }
it "displays the jobs" do
mock(subject).display("valid procfile detected (alpha, bravo)")
mock(subject).puts("valid procfile detected (alpha, bravo)")
subject.check
end
end
@@ -90,4 +122,53 @@ describe "Foreman::CLI" do
end
end
describe "run" do
describe "with a valid Procfile" do
before { write_procfile }
describe "and a command" do
let(:command) { ["ls", "-l"] }
before(:each) do
stub(subject).exec
end
it "should load the environment file" do
write_env
preserving_env do
subject.run *command
ENV["FOO"].should == "bar"
end
ENV["FOO"].should be_nil
end
it "should runute the command as a string" do
mock(subject).exec(command.join(" "))
subject.run *command
end
end
describe "and a non-existent command" do
let(:command) { "iuhtngrglhulhdfg" }
it "should print an error" do
mock_error(subject, "command not found: #{command}") do
subject.run command
end
end
end
describe "and a non-executable command" do
let(:command) { __FILE__ }
it "should print an error" do
mock_error(subject, "not executable: #{command}") do
subject.run command
end
end
end
end
end
end

View File

@@ -1,9 +1,16 @@
require "spec_helper"
require "foreman/engine"
describe "Foreman::Engine" do
describe "Foreman::Engine", :fakefs do
subject { Foreman::Engine.new("Procfile", {}) }
before do
any_instance_of(Foreman::Engine) do |engine|
stub(engine).proctitle
stub(engine).termtitle
end
end
describe "initialize" do
describe "without an existing Procfile" do
it "raises an error" do
@@ -24,8 +31,9 @@ describe "Foreman::Engine" do
describe "start" do
it "forks the processes" do
write_procfile
mock(subject).fork(subject.procfile["alpha"])
mock(subject).fork(subject.procfile["bravo"])
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./alpha", is_a(IO))
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO))
mock(subject).watch_for_output
mock(subject).watch_for_termination
subject.start
end
@@ -33,29 +41,14 @@ describe "Foreman::Engine" do
it "handles concurrency" do
write_procfile
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
mock(engine).fork_individual(engine.procfile["alpha"], 1, 5000)
mock(engine).fork_individual(engine.procfile["alpha"], 2, 5001)
mock(engine).fork_individual(engine.procfile["bravo"], 1, 5100)
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./alpha", is_a(IO)).twice
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO)).never
mock(engine).watch_for_output
mock(engine).watch_for_termination
engine.start
end
end
describe "execute" do
it "runs the processes" do
write_procfile
mock(subject).fork(subject.procfile["alpha"])
mock(subject).watch_for_termination
subject.execute("alpha")
end
it "shows an error running a process that doesnt exist" do
write_procfile
mock(subject).puts("ERROR: no such process: foo")
lambda { subject.execute("foo") }.should raise_error(SystemExit)
end
end
describe "environment" do
before(:each) do
write_procfile
@@ -66,9 +59,10 @@ describe "Foreman::Engine" do
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == {"FOO"=>"baz"}
engine.execute("alpha")
engine.start
end
it "should read more than one if specified" do
@@ -76,9 +70,10 @@ describe "Foreman::Engine" do
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
engine.execute("alpha")
engine.start
end
it "should fail if specified and doesnt exist" do
@@ -89,11 +84,29 @@ describe "Foreman::Engine" do
it "should read .env if none specified" do
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
engine = Foreman::Engine.new("Procfile")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
mock(engine).fork_individual(anything, anything, anything)
engine.environment.should == {"FOO"=>"qoo"}
engine.execute("bravo")
engine.start
end
end
describe "utf8" do
before(:each) do
File.open("Procfile", "w") do |file|
file.puts "utf8: #{resource_path("bin/utf8")}"
end
end
it "should spawn" do
stub(subject).watch_for_output
stub(subject).watch_for_termination
subject.start
Process.waitall
mock(subject).info(/started with pid \d+/, "utf8.1", anything)
mock(subject).info("\xff\x03\n", "utf8.1", anything)
subject.send(:poll_readers)
subject.send(:poll_readers)
end
end
end

View File

@@ -0,0 +1,22 @@
require "spec_helper"
require "foreman/export/base"
describe "Foreman::Export::Base" do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:location) { "/tmp/init" }
let(:engine) { Foreman::Engine.new(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 "export needs to be overridden" do
lambda { subject.export }.should raise_error("export method must be overridden")
end
it "raises errors as a Foreman::Export::Exception" do
lambda { subject.send(:error, "foo") }.should raise_error(Foreman::Export::Exception, "foo")
end
end

View File

@@ -3,18 +3,34 @@ require "foreman/engine"
require "foreman/export/bluepill"
require "tmpdir"
describe Foreman::Export::Bluepill do
describe Foreman::Export::Bluepill, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:engine) { Foreman::Engine.new(procfile) }
let(:bluepill) { Foreman::Export::Bluepill.new(engine) }
let(:engine) { Foreman::Engine.new(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("/tmp/init", :concurrency => "alpha=2")
File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill")
bluepill.export
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app.pill"))
end
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 concurrency" do
let(:options) { Hash[:concurrency => "alpha=2"] }
it "exports to the filesystem with concurrency" do
bluepill.export
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app-concurrency.pill"))
end
end
end

View File

@@ -0,0 +1,40 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/inittab"
require "tmpdir"
describe Foreman::Export::Inittab, :fakefs do
let(:location) { "/tmp/inittab" }
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:location) { "/tmp/inittab" }
let(:engine) { Foreman::Engine.new(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(:options) { Hash[:concurrency => "alpha=2"] }
it "exports to the filesystem with concurrency" do
inittab.export
File.read("/tmp/inittab").should == example_export_file("inittab/inittab.concurrency")
end
end
end

View File

@@ -3,33 +3,39 @@ require "foreman/engine"
require "foreman/export/runit"
require "tmpdir"
describe Foreman::Export::Runit do
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(procfile) }
let(:runit) { Foreman::Export::Runit.new(engine) }
let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, :concurrency => 'alpha=2,bravo=1') }
before(:each) { load_export_templates_into_fakefs("runit") }
before(:each) { stub(runit).say }
before(:each) { stub(FakeFS::FileUtils).chmod }
it "exports to the filesystem" do
FileUtils.mkdir_p('/tmp/init')
runit.export('/tmp/init', :concurrency => 'alpha=2')
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 ==
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 ==
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 ==
File.read("/tmp/init/app-bravo-1/log/run").should ==
example_export_file('runit/app-bravo-1-log-run')
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
end
end
it "creates a full path to the export directory" do
expect { runit.export }.to_not raise_error(Errno::ENOENT)
end
end

View File

@@ -3,27 +3,53 @@ require "foreman/engine"
require "foreman/export/upstart"
require "tmpdir"
describe Foreman::Export::Upstart do
describe Foreman::Export::Upstart, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:engine) { Foreman::Engine.new(procfile) }
let(:upstart) { Foreman::Export::Upstart.new(engine) }
let(:engine) { Foreman::Engine.new(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("/tmp/init", :concurrency => "alpha=2")
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.read("/tmp/init/app-bravo.conf").should == example_export_file("upstart/app-bravo.conf")
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf")
end
it "cleans up if exporting into an existing dir" do
mock(FileUtils).rm("/tmp/init/app.conf")
mock(FileUtils).rm("/tmp/init/app-alpha.conf")
mock(FileUtils).rm("/tmp/init/app-alpha-1.conf")
mock(FileUtils).rm("/tmp/init/app-bravo.conf")
mock(FileUtils).rm("/tmp/init/app-bravo-1.conf")
upstart.export
upstart.export
end
context "with concurrency" do
let(:options) { Hash[:concurrency => "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_root) { "/tmp/alternate" }
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, :template => template_root) }
before do
FileUtils.mkdir_p template_root
@@ -31,22 +57,28 @@ describe Foreman::Export::Upstart do
end
it "can export with alternate template files" do
upstart.export("/tmp/init", :template => template_root)
upstart.export
File.read("/tmp/init/app.conf").should == "alternate_template\n"
end
end
context "with alternate templates from home dir" do
let(:default_template_root) {File.expand_path("~/.foreman/templates")}
let(:default_template_root) {File.expand_path("#{ENV['HOME']}/.foreman/templates")}
before do
ENV['_FOREMAN_SPEC_HOME'] = ENV['HOME']
ENV['HOME'] = "/home/appuser"
FileUtils.mkdir_p default_template_root
File.open("#{default_template_root}/master.conf.erb", "w") { |f| f.puts "default_alternate_template" }
end
after do
ENV['HOME'] = ENV.delete('_FOREMAN_SPEC_HOME')
end
it "can export with alternate template files" do
upstart.export("/tmp/init")
upstart.export
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
end

View File

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

View 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

View File

@@ -1,2 +1,131 @@
require "spec_helper"
require "foreman/process"
require 'spec_helper'
require 'foreman/process'
require 'ostruct'
require 'timeout'
require 'tmpdir'
describe Foreman::Process do
subject { described_class.new entry, number, port }
let(:number) { 1 }
let(:port) { 777 }
let(:command) { "script" }
let(:name) { "foobar" }
let(:entry) { OpenStruct.new :name => name, :command => command }
its(:entry) { entry }
its(:num) { number }
its(:port) { port }
its(:name) { "#{name}.#{port}" }
its(:pid) { nil }
describe '#run' do
let(:pipe) { :pipe }
let(:basedir) { Dir.mktmpdir }
let(:env) {{ 'foo' => 'bar' }}
let(:init_delta) { 0.1 }
after { FileUtils.remove_entry_secure basedir }
def run(cmd=command)
entry.command = cmd
subject.run pipe, basedir, env
subject.detach && sleep(init_delta)
end
def run_file(executable, code)
file = File.open("#{basedir}/script", 'w') {|it| it << code }
run "#{executable} #{file.path}"
sleep 1
end
context 'options' do
it 'should set PORT for environment' do
mock(subject).run_process(basedir, command, pipe) do
ENV['PORT'].should == port.to_s
end
run
end
it 'should set custom variables for environment' do
mock(subject).run_process(basedir, command, pipe) do
ENV['foo'].should == 'bar'
end
run
end
it 'should restore environment afterwards' do
mock(subject).run_process(basedir, command, pipe)
run
ENV.should_not include('PORT', 'foo')
end
end
context 'process' do
around do |spec|
IO.pipe do |reader, writer|
@reader, @writer = reader, writer
spec.run
end
end
let(:pipe) { @writer }
let(:output) { @reader.read_nonblock 1024 }
it 'should not block' do
expect {
Timeout.timeout(2*init_delta) { run 'sleep 2' }
}.should_not raise_exception
end
it 'should be alive' do
run 'sleep 1'
subject.should be_alive
end
it 'should be dead' do
run 'exit'
subject.should be_dead
end
it 'should be killable' do
run 'sleep 1'
subject.kill 'TERM'
subject.should be_dead
end
it 'should send different signals' do
run_file 'ruby', <<-CODE
trap "TERM", "IGNORE"
loop { sleep 1 }
CODE
subject.should be_alive
subject.kill 'TERM'
subject.should be_alive
subject.kill 'KILL'
subject.should be_dead
end
it 'should redirect stdout' do
run 'echo hey'
output.should include('hey')
end
it 'should redirect stderr' do
run 'echo hey >2'
output.should include('hey')
end
it 'should handle variables' do
run 'echo $PORT'
output.should include('777')
end
it 'should handle arguments' do
pending
run %{ sh -c "trap '' TERM; sleep 10" }
subject.should be_alive
end
end
end
end

View File

@@ -8,4 +8,27 @@ describe Foreman do
it { should be_a String }
end
describe "::load_env!(env_file)", :fakefs do
after do
ENV['FOO'] = nil
end
it "should load env_file into ENV" do
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
Foreman.load_env!("/tmp/env1")
ENV['FOO'].should == 'bar'
end
it "should assume env_file in ./.env" do
File.open("./.env", "w") { |f| f.puts("FOO=bar") }
Foreman.load_env!
ENV['FOO'].should == 'bar'
end
end
describe "runner" do
it "should exist" do
File.exists?(Foreman.runner).should == true
end
end
end

18
spec/helper_spec.rb Normal file
View 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

2
spec/resources/bin/utf8 Executable file
View File

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

View File

@@ -0,0 +1,47 @@
Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "app"
app.gid = "app"
app.process("alpha-1") do |process|
process.start_command = "./alpha"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT" => "5000"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-alpha"
end
app.process("alpha-2") do |process|
process.start_command = "./alpha"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT" => "5001"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-alpha"
end
end

View File

@@ -19,31 +19,10 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-alpha"
end
app.process("alpha-2") do |process|
process.start_command = "./alpha"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT" => "5001"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-alpha"
end
app.process("bravo-1") do |process|
process.start_command = "./bravo"
@@ -57,7 +36,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-bravo"
end

View File

@@ -0,0 +1,4 @@
# ----- foreman app processes -----
AP01:4:respawn:/bin/su - app -c 'PORT=5000 ./alpha >> /var/log/app/alpha-1.log 2>&1'
AP02:4:respawn:/bin/su - app -c 'PORT=5001 ./alpha >> /var/log/app/alpha-2.log 2>&1'
# ----- end foreman app processes -----

View File

@@ -0,0 +1,4 @@
# ----- foreman app processes -----
AP01:4:respawn:/bin/su - app -c 'PORT=5000 ./alpha >> /var/log/app/alpha-1.log 2>&1'
AP02:4:respawn:/bin/su - app -c 'PORT=5100 ./bravo >> /var/log/app/bravo-1.log 2>&1'
# ----- end foreman app processes -----

View File

@@ -1,10 +1,20 @@
require "rubygems"
require "simplecov"
SimpleCov.start do
add_filter "/spec/"
end
require "rspec"
require "fakefs/safe"
require "fakefs/spec_helpers"
$:.unshift File.expand_path("../../lib", __FILE__)
def mock_export_error(message)
lambda { yield }.should raise_error(Foreman::Export::Exception, message)
end
def mock_error(subject, message)
mock_exit do
mock(subject).puts("ERROR: #{message}")
@@ -37,9 +47,11 @@ def write_procfile(procfile="Procfile", alpha_env="")
File.expand_path(procfile)
end
def write_env(env=".env")
def write_env(env=".env", options={"FOO"=>"bar"})
File.open(env, "w") do |file|
file.puts "FOO=bar"
options.each do |key, val|
file.puts "#{key}=#{val}"
end
end
end
@@ -56,15 +68,35 @@ def load_export_templates_into_fakefs(type)
end
end
def resource_path(filename)
File.expand_path("../resources/#{filename}", __FILE__)
end
def example_export_file(filename)
FakeFS.deactivate!
data = File.read(File.expand_path("../resources/export/#{filename}", __FILE__))
data = File.read(File.expand_path(resource_path("export/#{filename}"), __FILE__))
FakeFS.activate!
data
end
def preserving_env
old_env = ENV.to_hash
begin
yield
ensure
ENV.clear
ENV.update(old_env)
end
end
def normalize_space(s)
s.gsub(/\n[\n\s]*/, "\n")
end
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.color_enabled = true
config.include FakeFS::SpecHelpers
config.order = 'rand'
config.include FakeFS::SpecHelpers, :fakefs
config.mock_with :rr
end

112
tasks/dist.rake Normal file
View File

@@ -0,0 +1,112 @@
require "erb"
require "fileutils"
require "tmpdir"
def assemble(source, target, perms=0644)
FileUtils.mkdir_p(File.dirname(target))
File.open(target, "w") do |f|
f.puts ERB.new(File.read(source)).result(binding)
end
File.chmod(perms, target)
end
def assemble_distribution(target_dir=Dir.pwd)
distribution_files.each do |source|
target = source.gsub(/^#{project_root}/, target_dir)
FileUtils.mkdir_p(File.dirname(target))
FileUtils.cp(source, target)
end
end
GEM_BLACKLIST = %w( bundler foreman )
def assemble_gems(target_dir=Dir.pwd)
lines = %x{ cd #{project_root} && bundle show }.strip.split("\n")
raise "error running bundler" unless $?.success?
%x{ env BUNDLE_WITHOUT="development:test" bundle show }.split("\n").each do |line|
if line =~ /^ \* (.*?) \((.*?)\)/
next if GEM_BLACKLIST.include?($1)
puts "vendoring: #{$1}-#{$2}"
gem_dir = %x{ bundle show #{$1} }.strip
FileUtils.mkdir_p "#{target_dir}/vendor/gems"
%x{ cp -R "#{gem_dir}" "#{target_dir}/vendor/gems" }
end
end.compact
end
def beta?
Foreman::VERSION.to_s =~ /pre/
end
def clean(file)
rm file if File.exists?(file)
end
def distribution_files(type=nil)
require "foreman/distribution"
base_files = Foreman::Distribution.files
type_files = type ?
Dir[File.expand_path("../../dist/resources/#{type}/**/*", __FILE__)] : []
base_files.concat(type_files)
end
def mkchdir(dir)
FileUtils.mkdir_p(dir)
Dir.chdir(dir) do |dir|
yield(File.expand_path(dir))
end
end
def pkg(filename)
File.expand_path("../../pkg/#{filename}", __FILE__)
end
def project_root
File.expand_path("../..", __FILE__)
end
def resource(name)
File.expand_path("../../dist/resources/#{name}", __FILE__)
end
def s3_connect
return if @s3_connected
require "aws/s3"
unless ENV["DAVID_RELEASE_ACCESS"] && ENV["DAVID_RELEASE_SECRET"]
puts "please set DAVID_RELEASE_ACCESS and DAVID_RELEASE_SECRET in your environment"
exit 1
end
AWS::S3::Base.establish_connection!(
:access_key_id => ENV["DAVID_RELEASE_ACCESS"],
:secret_access_key => ENV["DAVID_RELEASE_SECRET"]
)
@s3_connected = true
end
def store(package_file, filename, bucket="assets.foreman.io")
s3_connect
puts "storing: #{filename}"
AWS::S3::S3Object.store(filename, File.open(package_file), bucket, :access => :public_read)
end
def tempdir
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
yield(dir)
end
end
end
def version
require "foreman/version"
Foreman::VERSION
end
Dir[File.expand_path("../../dist/**/*.rake", __FILE__)].each do |rake|
import rake
end

74
tasks/release.rake Normal file
View File

@@ -0,0 +1,74 @@
require "time"
desc "Build the manual"
task :man do
ENV['RONN_MANUAL'] = "Foreman Manual"
ENV['RONN_ORGANIZATION'] = "Foreman #{Foreman::VERSION}"
sh "ronn -w -s toc -r5 --markdown man/*.ronn"
end
desc "Commit the manual to git"
task "man:commit" => :man do
sh "git add README.md"
sh "git commit -am 'update docs' || echo 'nothing to commit'"
end
desc "Generate the Github docs"
task :pages => "man:commit" do
sh %{
cp man/foreman.1.html /tmp/foreman.1.html
git checkout gh-pages
rm ./index.html
cp /tmp/foreman.1.html ./index.html
git add -u index.html
git commit -m "saving man page to github docs"
git push origin -f gh-pages
git checkout master
}
end
desc "Generate an authors list"
task :authors do
authors = %x{ git log --pretty=format:"%an" | sort -u }.split("\n")
readme = File.read("README.md")
readme.gsub!(/#### Patches contributed by\n([^\n]*)\n/m, "#### Patches contributed by\n#{authors.join(", ")}\n")
File.open("README.md", "w") { |f| f.print readme }
end
def latest_release
latest = File.read("Changelog.md").split("\n").first.split(" ")[1]
end
def newer_release
tags = %x{ git tag --contains v#{latest_release} }.split("\n").sort_by do |tag|
Gem::Version.new(tag[1..-1])
end
tags.reject { |tag| Gem::Version.new(tag[1..-1]).prerelease? }[1]
end
desc "Generate a Changelog"
task :changelog do
while release = newer_release
entry = %x{ git show --format="%cd" #{release} | head -n 1 }
date = Time.parse(entry.chomp).strftime("%Y-%m-%d")
message = "## #{release[1..-1]} (#{date})\n\n"
message += %x{ git log --format="* %s [%an]" v#{latest_release}..#{release} }
changelog = File.read("Changelog.md")
changelog = message + "\n" + changelog
puts release
File.open("Changelog.md", "w") do |file|
file.print changelog
end
end
end
desc "Cut a release"
task :release do
Rake::Task["authors"].invoke
Rake::Task["changelog"].invoke
Rake::Task["pages"].invoke
end

8
tasks/rspec.rake Normal file
View File

@@ -0,0 +1,8 @@
require "rspec/core/rake_task"
task :default => :spec
desc "Run all specs"
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = 'spec/**/*_spec.rb'
end