Compare commits

...
100 Commits
Author SHA1 Message Date
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
David Dollar a5e094353c 0.26.1 2011-11-10 15:02:19 -05:00
David Dollar 12720b4c12 fix colors during execution of single process 2011-11-10 15:02:09 -05:00
David Dollar 1c732a4658 add runit export to docs 2011-11-08 18:00:23 -05:00
David Dollar 8908a66e90 0.26.0 2011-11-08 17:59:15 -05:00
David Dollar f63c0b0ca0 Merge pull request #95 from mlangenberg/runit-export
Add runit export format.
2011-11-08 14:58:27 -08:00
Matthijs Langenberg 676d3ff8d1 Add runit export format.
Fix #28
2011-11-08 23:50:33 +01:00
Mark McGranaghan 615aada17e sketch terminal title
Conflicts:

	lib/foreman/engine.rb
2011-11-08 13:23:06 -05:00
David Dollar 2e1d1c7c15 update docs 2011-11-08 12:02:50 -05:00
David Dollar bf832ffd9f Merge pull request #82 from trileuco/master
Documentation of new process name restrictions
2011-11-08 09:01:56 -08:00
David Dollar b9bfede48a Merge pull request #83 from argami/master
Controlling exception on ruby1.9.3-Head
2011-11-08 09:00:47 -08:00
David Dollar bed8323029 ensure concurrency=0 is handled during export 2011-11-08 11:58:03 -05:00
David Dollar f6ef5a5b4f display error when process doesnt exist and is run by name 2011-11-08 11:54:55 -05:00
David Dollar f3c1e76860 Merge pull request #92 from iain/patch-1
Processes is not a Hash, but an Array.
2011-11-08 08:45:38 -08: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
Iain Hecker caa688cdc6 Processes is not a Hash, but an Array. 2011-10-27 00:28:06 +03:00
David Dollar c6a410b664 Merge pull request #87 from clowder/fix_specs_passing_by_accident
Removed memoization [Foreman::Utils.parse_concurrency]
2011-10-19 10:07:42 -07:00
Chris Lowder 02c8d2cb10 Memoizing at the Class level wreaks havoc on the specs. 2011-10-19 18:02:49 +01:00
David Dollar ada41ce311 0.25.0 2011-10-17 16:21:15 -04:00
David Dollar 8f1c752a77 Merge pull request #85 from hlegendre/master
Allow numbers in the ENV variable keys
2011-10-12 14:20:50 -07:00
Hugues Le Gendre ddf25fe0ea Numbers should be allowed in key names of ENV no ? 2011-10-12 17:06:37 +03:00
Marcos Muino Garcia 9dac91a624 Added Procfile process name format documentation 2011-10-11 11:14:57 +02:00
Gamaliel Toro cdaeb646ac - Controlling non-existing command in ruby1.9.3-HEAD 2011-10-10 23:35:51 +02:00
David Dollar 86e4251840 add ability to test full comamnd line 2011-10-04 11:30:28 -04:00
David Dollar ba18f7e589 Merge pull request #74 from tomafro/master
Allow export using a specified environment file, using the --env or -e option
2011-10-04 08:20:04 -07:00
David Dollar be73e8500f 0.24.0 2011-10-04 10:43:33 -04:00
David Dollar d26ca669a1 define procfile regex, refactoring 2011-10-04 10:38:13 -04:00
David Dollar a3e758ab6c 0.23.1 2011-10-04 09:56:39 -04:00
David Dollar 5dc232a7b1 Merge pull request #79 from fdr/runner-dead-code
Eliminate dead code
2011-10-04 05:14:50 -07:00
Dan Farina 4191cb7b9c Eliminate dead code
An oversight; this code should probably have been pruned in
20e598abcc.

Signed-off-by: Dan Farina <drfarina@acm.org>
2011-10-04 00:19:04 -07:00
David Dollar 90d4dffb82 tweak docs 2011-09-16 18:46:10 -04:00
David Dollar 823f307abc doc updates 2011-09-16 18:42:57 -04:00
David Dollar ed44a11e21 0.23.0 2011-09-16 18:25:02 -04:00
David Dollar 5719f4fc72 allow multiple environment files to be specified 2011-09-16 18:24:38 -04:00
Tom Ward 47008fb921 Allow export using a specified environment file, using the --env or -e option 2011-09-16 08:31:29 +01:00
43 changed files with 747 additions and 256 deletions
+14
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:
- https://dx-helper.herokuapp.com/travis
+11
View File
@@ -0,0 +1,11 @@
0.26.1 12/05/2011 6160246da0fafe9cf8fde188d94bbc6babc667dc
==========================================================
Merge pull request #103 from csquared/load_env_from_irb [David Dollar]
refactor load_env to apply_environment [Chris Continanza]
rename load! to load_env! [Chris Continanza]
use ./.env as default [Chris Continanza]
load contents from env file [Chris Continanza]
refactor engine to expose env methods [Chris Continanza]
disable email notifications [David Dollar]
add travis config [David Dollar]
+2 -2
View File
@@ -6,10 +6,10 @@ group :development do
gem 'parka' gem 'parka'
gem 'rake' gem 'rake'
gem 'ronn' gem 'ronn'
gem 'fakefs', '~> 0.2.1' gem 'fakefs', '~> 0.3.2'
gem 'rcov', '~> 0.9.8' gem 'rcov', '~> 0.9.8'
gem 'rr', '~> 1.0.2' gem 'rr', '~> 1.0.2'
gem 'rspec', '~> 2.6.0' gem 'rspec', '~> 2.0'
gem 'aws-s3' gem 'aws-s3'
gem "rubyzip" gem "rubyzip"
end end
+14 -14
View File
@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
foreman (0.22.0) foreman (0.35.0)
term-ansicolor (~> 1.0.5) term-ansicolor (~> 1.0.5)
thor (>= 0.13.6) thor (>= 0.13.6)
@@ -14,8 +14,8 @@ GEM
xml-simple xml-simple
builder (3.0.0) builder (3.0.0)
crack (0.1.8) crack (0.1.8)
diff-lcs (1.1.2) diff-lcs (1.1.3)
fakefs (0.2.1) fakefs (0.3.2)
hpricot (0.8.2) hpricot (0.8.2)
mime-types (1.16) mime-types (1.16)
mustache (0.11.2) mustache (0.11.2)
@@ -23,7 +23,7 @@ GEM
crack crack
rest-client rest-client
thor thor
rake (0.9.2) rake (0.9.2.2)
rcov (0.9.8) rcov (0.9.8)
rdiscount (1.6.5) rdiscount (1.6.5)
rest-client (1.6.1) rest-client (1.6.1)
@@ -33,16 +33,16 @@ GEM
mustache (>= 0.7.0) mustache (>= 0.7.0)
rdiscount (>= 1.5.8) rdiscount (>= 1.5.8)
rr (1.0.2) rr (1.0.2)
rspec (2.6.0) rspec (2.8.0)
rspec-core (~> 2.6.0) rspec-core (~> 2.8.0)
rspec-expectations (~> 2.6.0) rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.6.0) rspec-mocks (~> 2.8.0)
rspec-core (2.6.4) rspec-core (2.8.0)
rspec-expectations (2.6.0) rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2) diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0) rspec-mocks (2.8.0)
rubyzip (0.9.4) rubyzip (0.9.4)
term-ansicolor (1.0.6) term-ansicolor (1.0.7)
thor (0.14.6) thor (0.14.6)
xml-simple (1.0.15) xml-simple (1.0.15)
@@ -51,12 +51,12 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
aws-s3 aws-s3
fakefs (~> 0.2.1) fakefs (~> 0.3.2)
foreman! foreman!
parka parka
rake rake
rcov (~> 0.9.8) rcov (~> 0.9.8)
ronn ronn
rr (~> 1.0.2) rr (~> 1.0.2)
rspec (~> 2.6.0) rspec (~> 2.0)
rubyzip rubyzip
+2 -1
View File
@@ -20,7 +20,8 @@ http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
## Manual ## Manual
See the [man page](http://ddollar.github.com/foreman) for usage. * [man page](http://ddollar.github.com/foreman)
* [wiki](http://github.com/ddollar/foreman/wiki)
## Authorship ## Authorship
+19
View File
@@ -166,3 +166,22 @@ end
Dir[File.expand_path("../dist/**/*.rake", __FILE__)].each do |rake| Dir[File.expand_path("../dist/**/*.rake", __FILE__)].each do |rake|
import rake import rake
end end
task :changelog do
timestamp = Time.now.utc.strftime('%m/%d/%Y')
sha = `git log | head -1`.split(' ').last
changelog = ["#{version} #{timestamp} #{sha}"]
changelog << ('=' * changelog[0].length)
changelog << ''
last_sha = `cat Changelog | head -1`.split(' ').last
shortlog = `git log #{last_sha}..HEAD --pretty=format:'%s [%an]'`
changelog << shortlog.split("\n")
changelog.concat ['', '', '']
old_changelog = File.read('Changelog')
File.open('Changelog', 'w') do |file|
file.write(changelog.join("\n"))
file.write(old_changelog)
end
end
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exec $1 2>&1
+1 -1
View File
@@ -1,2 +1,2 @@
ticker: ruby ./ticker $PORT ticker: ruby ./ticker $PORT
error : ruby ./error error: ruby ./error
+3 -3
View File
@@ -3,7 +3,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
app.uid = "<%= user %>" app.uid = "<%= user %>"
app.gid = "<%= user %>" app.gid = "<%= user %>"
<% engine.processes.values.each do |process| %> <% engine.procfile.entries.each do |process| %>
<% 1.upto(concurrency[process.name]) do |num| %> <% 1.upto(concurrency[process.name]) do |num| %>
<% port = engine.port_for(process, num, options[:port]) %> <% port = engine.port_for(process, num, options[:port]) %>
app.process("<%= process.name %>-<%=num%>") do |process| app.process("<%= process.name %>-<%=num%>") do |process|
@@ -19,9 +19,9 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
process.monitor_children do |children| process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}" children.stop_command "kill -QUIT {{PID}}"
end end
process.group = "<%= app %>-<%= process.name %>" process.group = "<%= app %>-<%= process.name %>"
end end
<% end %> <% end %>
<% end %> <% end %>
end end
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
LOG=<%= log_root %>/<%= process.name %>-<%= num %>
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
exec chpst -u <%= user %> svlogd "$LOG"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
cd <%= engine.directory %>
exec chpst -u <%= user %> -e <%= process_env_directory %> <%= process.command %>
+10
View File
@@ -4,5 +4,15 @@ module Foreman
class AppDoesNotExist < Exception; end class AppDoesNotExist < Exception; end
# load contents of env_file into ENV
def self.load_env!(env_file = './.env')
require 'foreman/engine'
Foreman::Engine.load_env!(env_file)
end
def self.runner
File.expand_path("../../bin/runner", __FILE__)
end
end end
+30 -13
View File
@@ -6,28 +6,33 @@ require "yaml"
class Foreman::CLI < Thor class Foreman::CLI < Thor
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile" desc "start", "Start the application"
desc "start [PROCESS]", "Start the application, or a specific process" class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
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 :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :port, :type => :numeric, :aliases => "-p" method_option :port, :type => :numeric, :aliases => "-p"
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"' method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
def start(process=nil) class << self
check_procfile! # Hackery. Take the run method away from Thor so that we can redefine it.
def is_thor_reserved_word?(word, type)
if process return false if word == 'run'
engine.execute(process) super
else
engine.start
end end
end end
def start
check_procfile!
engine.start
end
desc "export FORMAT LOCATION", "Export the application to another process management format" desc "export FORMAT LOCATION", "Export the application to another process management format"
method_option :app, :type => :string, :aliases => "-a" method_option :app, :type => :string, :aliases => "-a"
method_option :log, :type => :string, :aliases => "-l" method_option :log, :type => :string, :aliases => "-l"
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :port, :type => :numeric, :aliases => "-p" method_option :port, :type => :numeric, :aliases => "-p"
method_option :user, :type => :string, :aliases => "-u" method_option :user, :type => :string, :aliases => "-u"
method_option :template, :type => :string, :aliases => "-t" method_option :template, :type => :string, :aliases => "-t"
@@ -41,6 +46,7 @@ class Foreman::CLI < Thor
when "inittab" then Foreman::Export::Inittab when "inittab" then Foreman::Export::Inittab
when "upstart" then Foreman::Export::Upstart when "upstart" then Foreman::Export::Upstart
when "bluepill" then Foreman::Export::Bluepill when "bluepill" then Foreman::Export::Bluepill
when "runit" then Foreman::Export::Runit
else error "Unknown export format: #{format}." else error "Unknown export format: #{format}."
end end
@@ -53,9 +59,21 @@ class Foreman::CLI < Thor
desc "check", "Validate your application's Procfile" desc "check", "Validate your application's Procfile"
def check def check
processes = engine.processes_in_order.map { |p| p.first } error "no processes defined" unless engine.procfile.entries.length > 0
error "no processes defined" unless processes.length > 0 display "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
display "valid procfile detected (#{processes.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 end
private ###################################################################### private ######################################################################
@@ -91,5 +109,4 @@ private ######################################################################
defaults = YAML::load_file(".foreman") || {} defaults = YAML::load_file(".foreman") || {}
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options)) Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
end end
end end
+129 -139
View File
@@ -1,145 +1,137 @@
require "foreman" require "foreman"
require "foreman/process" require "foreman/process"
require "foreman/procfile"
require "foreman/utils" require "foreman/utils"
require "pty" require "pty"
require "tempfile" require "tempfile"
require "timeout" require "timeout"
require "term/ansicolor" require "term/ansicolor"
require "fileutils" require "fileutils"
require "thread"
class Foreman::Engine class Foreman::Engine
attr_reader :procfile attr_reader :procfile
attr_reader :directory attr_reader :directory
attr_reader :environment
attr_reader :options attr_reader :options
extend Term::ANSIColor 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={}) def initialize(procfile, options={})
@procfile = read_procfile(procfile) @procfile = Foreman::Procfile.new(procfile)
@directory = File.expand_path(File.dirname(procfile)) @directory = options[:app_root] || File.expand_path(File.dirname(procfile))
@options = options @options = options
@environment = read_environment(options[:env]) @environment = read_environment_files(options[:env])
@output_mutex = Mutex.new
end end
def processes def self.load_env!(env_file)
@processes ||= begin @environment = read_environment_files(env_file)
@order = [] apply_environment!
procfile.split("\n").inject({}) do |hash, line|
next hash if line.strip == ""
name, command = line.split(/\s*:\s+/, 2)
unless command
warn_deprecated_procfile!
name, command = line.split(/ +/, 2)
end
process = Foreman::Process.new(name, command)
process.color = next_color
@order << process.name
hash.update(process.name => process)
end
end
end
def process_order
processes
@order.uniq
end
def processes_in_order
process_order.map do |name|
[name, processes[name]]
end
end end
def start def start
proctitle "ruby: foreman master" proctitle "ruby: foreman master"
termtitle "#{File.basename(@directory)} - foreman"
processes_in_order.each do |name, process|
fork process
end
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully }
watch_for_termination
end
def execute(name)
fork processes[name]
trap("TERM") { puts "SIGTERM received"; terminate_gracefully } trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully } trap("INT") { puts "SIGINT received"; terminate_gracefully }
assign_colors
spawn_processes
watch_for_output
watch_for_termination watch_for_termination
end end
def port_for(process, num, base_port=nil) def port_for(process, num, base_port=nil)
base_port ||= 5000 base_port ||= 5000
offset = processes_in_order.map { |p| p.first }.index(process.name) * 100 offset = procfile.process_names.index(process.name) * 100
base_port.to_i + offset + num - 1 base_port.to_i + offset + num - 1
end end
private ###################################################################### private ######################################################################
def fork(process) def spawn_processes
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency]) concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
1.upto(concurrency[process.name]) do |num| procfile.entries.each do |entry|
fork_individual(process, num, port_for(process, num, @options[:port])) reader, writer = IO.pipe
entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
running_processes[process.pid] = process
readers[process] = reader
end
end end
end end
def fork_individual(process, num, port) def base_port
@environment.each { |k,v| ENV[k] = v } options[:port] || 5000
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
begin
info "process exiting", process
rescue Interrupt
end
end
end end
def kill_all(signal="SIGTERM") def kill_all(signal="SIGTERM")
running_processes.each do |pid, process| running_processes.each do |pid, process|
info "sending #{signal} to pid #{pid}"
Process.kill(signal, pid) rescue Errno::ESRCH Process.kill(signal, pid) rescue Errno::ESRCH
end end
end end
def info(message, process=nil) def terminate_gracefully
print process.color if process info "sending SIGTERM to all processes"
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | " kill_all "SIGTERM"
Timeout.timeout(5) { Process.waitall }
rescue Timeout::Error
info "sending SIGKILL to all processes"
kill_all "SIGKILL"
end
def watch_for_output
Thread.new do
begin
loop do
rs, ws = IO.select(readers.values, [], [], 1)
(rs || []).each do |r|
ps, message = r.gets.split(",", 2)
color = colors[ps.split(".").first]
info message, ps, color
end
end
rescue Exception => ex
puts ex.message
puts ex.backtrace
end
end
end
def watch_for_termination
pid, status = Process.wait2
process = running_processes.delete(pid)
info "process terminated", process.name
terminate_gracefully
kill_all
rescue Errno::ECHILD
end
def info(message, name="system", color=Term::ANSIColor.white)
print color
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
print Term::ANSIColor.reset print Term::ANSIColor.reset
print message.chomp print message.chomp
puts puts ""
end
def print(message=nil)
@output_mutex.synchronize do
$stdout.print message
end
end
def puts(message=nil)
@output_mutex.synchronize do
$stdout.puts message
end
end end
def error(message) def error(message)
@@ -149,86 +141,84 @@ private ######################################################################
def longest_process_name def longest_process_name
@longest_process_name ||= begin @longest_process_name ||= begin
longest = processes.keys.map { |name| name.length }.sort.last longest = procfile.process_names.map { |name| name.length }.sort.last
longest = 6 if longest < 6 # system longest = 6 if longest < 6 # system
longest longest
end end
end end
def pad_process_name(process) def pad_process_name(name="system")
name = process ? "#{ENV["PS"]}" : "system" name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
name.ljust(longest_process_name + 3) # add 3 for process number padding
end
def print_info
info "currently running processes:"
running_processes.each do |pid, process|
info "pid #{pid}", process
end
end end
def proctitle(title) def proctitle(title)
$0 = title $0 = title
end end
def read_procfile(procfile) def termtitle(title)
File.read(procfile) printf("\033]0;#{title}\007")
end
def watch_for_termination
pid, status = Process.wait2
process = running_processes.delete(pid)
info "process terminated", process
terminate_gracefully
kill_all
rescue Errno::ECHILD
end end
def running_processes def running_processes
@running_processes ||= {} @running_processes ||= {}
end 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 def next_color
@current_color ||= -1 @current_color ||= -1
@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 end
def warn_deprecated_procfile! module Env
return if @already_warned_deprecated attr_reader :environment
@already_warned_deprecated = true
puts "!!! This format of Procfile is deprecated, and will not work starting in v0.12"
puts "!!! Use a colon to separate the process name from the command"
puts "!!! e.g. web: thin start"
end
def read_environment(filename) def read_environment_files(filenames)
error "No such file: #{filename}" if filename && !File.exists?(filename) environment = {}
filename ||= ".env"
environment = {}
if File.exists?(filename) (filenames || "").split(",").map(&:strip).each do |filename|
File.read(filename).split("\n").each do |line| error "No such file: #{filename}" unless File.exists?(filename)
if line =~ /\A([A-Za-z_]+)=(.*)\z/ environment.merge!(read_environment(filename))
environment[$1] = $2 end
environment.merge!(read_environment(".env")) unless filenames
environment
end
def read_environment(filename)
return {} unless File.exists?(filename)
File.read(filename).split("\n").inject({}) do |hash, line|
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
hash[$1] = $2
end end
hash
end end
end end
environment def apply_environment!
end @environment.each { |k,v| ENV[k] = v }
end
def runner
File.expand_path("../../../bin/foreman-runner", __FILE__)
end
def terminate_gracefully
info "sending SIGTERM to all processes"
kill_all "SIGTERM"
Timeout.timeout(3) { Process.waitall }
rescue Timeout::Error
info "sending SIGKILL to all processes"
kill_all "SIGKILL"
end end
include Env
extend Env
end end
+1
View File
@@ -8,3 +8,4 @@ require "foreman/export/base"
require "foreman/export/inittab" require "foreman/export/inittab"
require "foreman/export/upstart" require "foreman/export/upstart"
require "foreman/export/bluepill" require "foreman/export/bluepill"
require "foreman/export/runit"
+1 -1
View File
@@ -26,7 +26,7 @@ private ######################################################################
def export_template(exporter, file, template_root) def export_template(exporter, file, template_root)
if template_root && File.exist?(file_path = File.join(template_root, file)) if template_root && File.exist?(file_path = File.join(template_root, file))
File.read(file_path) 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) File.read(file_path)
else else
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__)) File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
-1
View File
@@ -23,7 +23,6 @@ class Foreman::Export::Bluepill < Foreman::Export::Base
master_template = export_template("bluepill", "master.pill.erb", template_root) master_template = export_template("bluepill", "master.pill.erb", template_root)
master_config = ERB.new(master_template).result(binding) master_config = ERB.new(master_template).result(binding)
write_file "#{location}/#{app}.pill", master_config write_file "#{location}/#{app}.pill", master_config
end end
end end
+1 -1
View File
@@ -12,7 +12,7 @@ class Foreman::Export::Inittab < Foreman::Export::Base
inittab = [] inittab = []
inittab << "# ----- foreman #{app} processes -----" inittab << "# ----- foreman #{app} processes -----"
engine.processes.values.inject(1) do |index, process| engine.procfile.entries.inject(1) do |index, process|
1.upto(concurrency[process.name]) do |num| 1.upto(concurrency[process.name]) do |num|
id = app.slice(0, 2).upcase + sprintf("%02d", index) id = app.slice(0, 2).upcase + sprintf("%02d", index)
port = engine.port_for(process, num, options[:port]) port = engine.port_for(process, num, options[:port])
+61
View File
@@ -0,0 +1,61 @@
require "erb"
require "foreman/export"
class Foreman::Export::Runit < Foreman::Export::Base
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
def export(location, options={})
error("Must specify a location") unless location
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template]
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
run_template = export_template('runit', 'run.erb', template_root)
log_run_template = export_template('runit', 'log_run.erb', template_root)
engine.procfile.entries.each do |process|
1.upto(concurrency[process.name]) do |num|
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
process_env_directory = "#{process_directory}/env"
process_log_directory = "#{process_directory}/log"
create_directory process_directory
create_directory process_env_directory
create_directory process_log_directory
run = ERB.new(run_template).result(binding)
write_file "#{process_directory}/run", run
FileUtils.chmod 0755, "#{process_directory}/run"
port = engine.port_for(process, num, options[:port])
environment_variables = {'PORT' => port}.
merge(engine.environment).
merge(inline_variables(process.command))
environment_variables.each_pair do |var, env|
write_file "#{process_env_directory}/#{var.upcase}", env
end
log_run = ERB.new(log_run_template).result(binding)
write_file "#{process_log_directory}/run", log_run
FileUtils.chmod 0755, "#{process_log_directory}/run"
end
end
end
private
def create_directory(location)
say "creating: #{location}"
FileUtils.mkdir(location)
end
def inline_variables(command)
variable_name_regex =
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
end
end
+2 -1
View File
@@ -26,7 +26,8 @@ class Foreman::Export::Upstart < Foreman::Export::Base
process_template = export_template("upstart", "process.conf.erb", template_root) process_template = export_template("upstart", "process.conf.erb", template_root)
engine.processes.values.each do |process| engine.procfile.entries.each do |process|
next if (conc = concurrency[process.name]) < 1
process_master_template = export_template("upstart", "process_master.conf.erb", template_root) process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
process_master_config = ERB.new(process_master_template).result(binding) process_master_config = ERB.new(process_master_template).result(binding)
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
+60 -6
View File
@@ -2,13 +2,67 @@ require "foreman"
class Foreman::Process class Foreman::Process
attr_reader :name attr_reader :entry
attr_reader :command attr_reader :num
attr_accessor :color attr_reader :pid
attr_reader :port
def initialize(name, command) def initialize(entry, num, port)
@name = name @entry = entry
@command = command @num = num
@port = port
end
def run(pipe, basedir, environment)
Dir.chdir(basedir) do
with_environment(environment.merge("PORT" => port.to_s)) do
run_process entry.command, pipe
end
end
end
def name
"%s.%s" % [ entry.name, num ]
end
private
def fork_with_io(command)
reader, writer = IO.pipe
pid = fork do
trap("INT", "IGNORE")
$stdout.reopen writer
reader.close
exec Foreman.runner, replace_command_env(command)
end
[ reader, pid ]
end
def run_process(command, pipe)
io, @pid = fork_with_io(command)
output pipe, "started with pid %d" % @pid
Thread.new do
until io.eof?
output pipe, io.gets
end
end
end
def output(pipe, message)
pipe.puts "%s,%s" % [ name, message ]
end
def replace_command_env(command)
command.gsub(/\$(\w+)/) { |e| ENV[e[1..-1]] }
end
def with_environment(environment)
old_env = ENV.each_pair.inject({}) { |h,(k,v)| h.update(k => v) }
environment.each { |k,v| ENV[k] = v }
ret = yield
ENV.clear
old_env.each { |k,v| ENV[k] = v}
ret
end end
end end
+38
View File
@@ -0,0 +1,38 @@
require "foreman"
require "foreman/procfile_entry"
# A valid Procfile entry is captured by this regex.
# All other lines are ignored.
#
# /^([A-Za-z0-9_]+):\s*(.+)$/
#
# $1 = name
# $2 = command
#
class Foreman::Procfile
attr_reader :entries
def initialize(filename)
@entries = parse_procfile(filename)
end
def [](name)
entries.detect { |entry| entry.name == name }
end
def process_names
entries.map(&:name)
end
private
def parse_procfile(filename)
File.read(filename).split("\n").map do |line|
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
Foreman::ProcfileEntry.new($1, $2)
end
end.compact
end
end
+22
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
+5 -2
View File
@@ -3,9 +3,12 @@ require "foreman"
class Foreman::Utils class Foreman::Utils
def self.parse_concurrency(concurrency) def self.parse_concurrency(concurrency)
@concurrency ||= begin begin
pairs = concurrency.to_s.gsub(/\s/, "").split(",") 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("=") process, amount = pair.split("=")
hash.update(process => amount.to_i) hash.update(process => amount.to_i)
end end
+1 -1
View File
@@ -1,5 +1,5 @@
module Foreman module Foreman
VERSION = "0.22.0" VERSION = "0.35.0"
end end
+11 -4
View File
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
.TH "FOREMAN" "1" "September 2011" "Foreman 0.21.0" "Foreman Manual" .TH "FOREMAN" "1" "January 2012" "Foreman 0.33.1" "Foreman Manual"
. .
.SH "NAME" .SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications \fBforeman\fR \- manage Procfile\-based applications
@@ -68,8 +68,12 @@ Specify the user the application should be run as\. Defaults to the app name
These options control all modes of foreman\'s operation\. These options control all modes of foreman\'s operation\.
. .
.TP .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 \fB\-e\fR, \fB\-\-env\fR
Specify an alternate environment file\. Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
. .
.TP .TP
\fB\-f\fR, \fB\-\-procfile\fR \fB\-f\fR, \fB\-\-procfile\fR
@@ -85,6 +89,9 @@ bluepill
inittab inittab
. .
.IP "\(bu" 4 .IP "\(bu" 4
runit
.
.IP "\(bu" 4
upstart upstart
. .
.IP "" 0 .IP "" 0
@@ -132,7 +139,7 @@ job: bundle exec rake jobs:work
.IP "" 0 .IP "" 0
. .
.P .P
You can validate your Procfile format using the \fBcheck\fR command A process name may contain letters, numbers amd the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
. .
.IP "" 4 .IP "" 4
. .
@@ -165,7 +172,7 @@ If a \fB\.foreman\fR file exists in the current directory, default options will
. .
.nf .nf
concurrency: alpha=0 concurrency: alpha=0,bravo=1
port: 15000 port: 15000
. .
.fi .fi
+11 -3
View File
@@ -66,8 +66,13 @@ The following options control how the application is run:
These options control all modes of foreman's operation. 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`: * `-e`, `--env`:
Specify an alternate environment file. Specify an alternate environment file. You can specify more than one
file by using: `--env file1,file2`.
* `-f`, `--procfile`: * `-f`, `--procfile`:
Specify an alternate location for the application's Procfile. This file's Specify an alternate location for the application's Procfile. This file's
@@ -82,6 +87,8 @@ foreman currently supports the following output formats:
* inittab * inittab
* runit
* upstart * upstart
## INITTAB EXPORT ## INITTAB EXPORT
@@ -112,7 +119,8 @@ to run it.
web: bundle exec thin start web: bundle exec thin start
job: bundle exec rake jobs:work job: bundle exec rake jobs:work
You can validate your Procfile format using the `check` command A process name may contain letters, numbers amd the underscore character.
You can validate your Procfile format using the `check` command:
$ foreman check $ foreman check
@@ -131,7 +139,7 @@ If a `.foreman` file exists in the current directory, default options will
be read from it. This file should be in YAML format with the long option be read from it. This file should be in YAML format with the long option
name as keys. Example: name as keys. Example:
concurrency: alpha=0 concurrency: alpha=0,bravo=1
port: 15000 port: 15000
## EXAMPLES ## EXAMPLES
+58
View File
@@ -26,6 +26,15 @@ describe "Foreman::CLI" do
end end
describe "export" do describe "export" do
describe "options" do
it "respects --env" do
write_procfile
write_env("envfile")
mock.instance_of(Foreman::Export::Upstart).export("/upstart", { "env" => "envfile" })
foreman %{ export upstart /upstart --env envfile }
end
end
describe "with a non-existent Procfile" do describe "with a non-existent Procfile" do
it "prints an error" do it "prints an error" do
mock_error(subject, "Procfile does not exist.") do mock_error(subject, "Procfile does not exist.") do
@@ -80,5 +89,54 @@ describe "Foreman::CLI" do
end end
end end
end end
describe "run" do
describe "with a valid Procfile" do
before { write_procfile }
describe "and a command" do
let(:command) { ["ls", "-l"] }
before(:each) do
stub(subject).exec
end
it "should load the environment file" do
write_env
preserving_env do
subject.run *command
ENV["FOO"].should == "bar"
end
ENV["FOO"].should be_nil
end
it "should runute the command as a string" do
mock(subject).exec(command.join(" "))
subject.run *command
end
end
describe "and a non-existent command" do
let(:command) { "iuhtngrglhulhdfg" }
it "should print an error" do
mock_error(subject, "command not found: #{command}") do
subject.run command
end
end
end
describe "and a non-executable command" do
let(:command) { __FILE__ }
it "should print an error" do
mock_error(subject, "not executable: #{command}") do
subject.run command
end
end
end
end
end
end end
+23 -34
View File
@@ -15,21 +15,8 @@ describe "Foreman::Engine" do
before { write_procfile } before { write_procfile }
it "reads the processes" do it "reads the processes" do
subject.processes["alpha"].command.should == "./alpha" subject.procfile["alpha"].command.should == "./alpha"
subject.processes["bravo"].command.should == "./bravo" subject.procfile["bravo"].command.should == "./bravo"
end
end
describe "with a deprecated Procfile" do
before do
File.open("Procfile", "w") do |file|
file.puts "name command"
end
end
it "should print a deprecation warning" do
mock(subject).warn_deprecated_procfile!
subject.processes.length.should == 1
end end
end end
end end
@@ -37,8 +24,9 @@ describe "Foreman::Engine" do
describe "start" do describe "start" do
it "forks the processes" do it "forks the processes" do
write_procfile write_procfile
mock(subject).fork(subject.processes["alpha"]) mock.instance_of(Foreman::Process).run_process("./alpha", is_a(IO))
mock(subject).fork(subject.processes["bravo"]) mock.instance_of(Foreman::Process).run_process("./bravo", is_a(IO))
mock(subject).watch_for_output
mock(subject).watch_for_termination mock(subject).watch_for_termination
subject.start subject.start
end end
@@ -46,25 +34,15 @@ describe "Foreman::Engine" do
it "handles concurrency" do it "handles concurrency" do
write_procfile write_procfile
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2") engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
mock(engine).fork_individual(engine.processes["alpha"], 1, 5000) mock.instance_of(Foreman::Process).run_process("./alpha", is_a(IO)).twice
mock(engine).fork_individual(engine.processes["alpha"], 2, 5001) mock.instance_of(Foreman::Process).run_process("./bravo", is_a(IO)).never
mock(engine).fork_individual(engine.processes["bravo"], 1, 5100) mock(engine).watch_for_output
mock(engine).watch_for_termination mock(engine).watch_for_termination
engine.start engine.start
end end
end end
describe "execute" do
it "runs the processes" do
write_procfile
mock(subject).fork(subject.processes["alpha"])
mock(subject).watch_for_termination
subject.execute("alpha")
end
end
describe "environment" do describe "environment" do
before(:each) do before(:each) do
write_procfile write_procfile
stub(Process).fork stub(Process).fork
@@ -74,9 +52,21 @@ describe "Foreman::Engine" do
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") } File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env") engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
stub(engine).info stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination mock(engine).watch_for_termination
engine.environment.should == {"FOO"=>"baz"} engine.environment.should == {"FOO"=>"baz"}
engine.execute("alpha") engine.start
end
it "should read more than one if specified" do
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
engine.start
end end
it "should fail if specified and doesnt exist" do it "should fail if specified and doesnt exist" do
@@ -87,11 +77,10 @@ describe "Foreman::Engine" do
it "should read .env if none specified" do it "should read .env if none specified" do
File.open(".env", "w") { |f| f.puts("FOO=qoo") } File.open(".env", "w") { |f| f.puts("FOO=qoo") }
engine = Foreman::Engine.new("Procfile") engine = Foreman::Engine.new("Procfile")
stub(engine).info mock(engine).spawn_processes
mock(engine).watch_for_termination mock(engine).watch_for_termination
mock(engine).fork_individual(anything, anything, anything)
engine.environment.should == {"FOO"=>"qoo"} engine.environment.should == {"FOO"=>"qoo"}
engine.execute("bravo") engine.start
end end
end end
end end
+6 -2
View File
@@ -13,8 +13,12 @@ describe Foreman::Export::Bluepill do
it "exports to the filesystem" do it "exports to the filesystem" do
bluepill.export("/tmp/init") bluepill.export("/tmp/init")
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app.pill"))
File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill")
end end
it "exports to the filesystem with concurrency" do
bluepill.export("/tmp/init", :concurrency => "alpha=2")
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app-concurrency.pill"))
end
end end
+36
View File
@@ -0,0 +1,36 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/runit"
require "tmpdir"
describe Foreman::Export::Runit do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
let(:engine) { Foreman::Engine.new(procfile) }
let(:runit) { Foreman::Export::Runit.new(engine) }
before(:each) { load_export_templates_into_fakefs("runit") }
before(:each) { stub(runit).say }
before(:each) { stub(FakeFS::FileUtils).chmod }
it "exports to the filesystem" do
FileUtils.mkdir_p('/tmp/init')
runit.export('/tmp/init', :concurrency => "alpha=2,bravo=1")
File.read("/tmp/init/app-alpha-1/run").should == example_export_file('runit/app-alpha-1-run')
File.read("/tmp/init/app-alpha-1/log/run").should ==
example_export_file('runit/app-alpha-1-log-run')
File.read("/tmp/init/app-alpha-1/env/PORT").should == "5000\n"
File.read("/tmp/init/app-alpha-1/env/BAR").should == "baz\n"
File.read("/tmp/init/app-alpha-2/run").should == example_export_file('runit/app-alpha-2-run')
File.read("/tmp/init/app-alpha-2/log/run").should ==
example_export_file('runit/app-alpha-2-log-run')
File.read("/tmp/init/app-alpha-2/env/PORT").should == "5001\n"
File.read("/tmp/init/app-alpha-2/env/BAR").should == "baz\n"
File.read("/tmp/init/app-bravo-1/run").should == example_export_file('runit/app-bravo-1-run')
File.read("/tmp/init/app-bravo-1/log/run").should ==
example_export_file('runit/app-bravo-1-log-run')
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
end
end
+17 -2
View File
@@ -17,11 +17,20 @@ describe Foreman::Export::Upstart do
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf") 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.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-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.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") File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf")
end end
it "exports to the filesystem with concurrency" do
upstart.export("/tmp/init", :concurrency => "alpha=2")
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("upstart/app-alpha-2.conf")
File.exists?("/tmp/init/app-bravo-1.conf").should == false
end
context "with alternate templates" do context "with alternate templates" do
let(:template_root) { "/tmp/alternate" } let(:template_root) { "/tmp/alternate" }
@@ -38,13 +47,19 @@ describe Foreman::Export::Upstart do
end end
context "with alternate templates from home dir" do context "with alternate templates from home dir" do
let(:default_template_root) {File.expand_path("~/.foreman/templates")} let(:default_template_root) {File.expand_path("#{ENV['HOME']}/.foreman/templates")}
before do before do
ENV['_FOREMAN_SPEC_HOME'] = ENV['HOME']
ENV['HOME'] = "/home/appuser"
FileUtils.mkdir_p default_template_root FileUtils.mkdir_p default_template_root
File.open("#{default_template_root}/master.conf.erb", "w") { |f| f.puts "default_alternate_template" } File.open("#{default_template_root}/master.conf.erb", "w") { |f| f.puts "default_alternate_template" }
end end
after do
ENV['HOME'] = ENV.delete('_FOREMAN_SPEC_HOME')
end
it "can export with alternate template files" do it "can export with alternate template files" do
upstart.export("/tmp/init") upstart.export("/tmp/init")
+22
View File
@@ -8,4 +8,26 @@ describe Foreman do
it { should be_a String } it { should be_a String }
end end
describe "::load_env!(env_file)" do
before do
FakeFS.activate!
end
after do
FakeFS.deactivate!
ENV['FOO'] = nil
end
it "should load env_file into ENV" do
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
Foreman.load_env!("/tmp/env1")
ENV['FOO'].should == 'bar'
end
it "should assume env_file in ./.env" do
File.open("./.env", "w") { |f| f.puts("FOO=bar") }
Foreman.load_env!
ENV['FOO'].should == 'bar'
end
end
end end
+18
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
@@ -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
+2 -23
View File
@@ -19,31 +19,10 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children| process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}" children.stop_command "kill -QUIT {{PID}}"
end end
process.group = "app-alpha" process.group = "app-alpha"
end 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| app.process("bravo-1") do |process|
process.start_command = "./bravo" process.start_command = "./bravo"
@@ -57,7 +36,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
process.monitor_children do |children| process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}" children.stop_command "kill -QUIT {{PID}}"
end end
process.group = "app-bravo" process.group = "app-bravo"
end end
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
LOG=/var/log/app/alpha-1
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
@@ -0,0 +1,3 @@
#!/bin/sh
cd /tmp/app
exec chpst -u app -e /tmp/init/app-alpha-1/env ./alpha bar=baz
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
LOG=/var/log/app/alpha-2
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
@@ -0,0 +1,3 @@
#!/bin/sh
cd /tmp/app
exec chpst -u app -e /tmp/init/app-alpha-2/env ./alpha bar=baz
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
LOG=/var/log/app/bravo-1
test -d "$LOG" || mkdir -p m2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
@@ -0,0 +1,3 @@
#!/bin/sh
cd /tmp/app
exec chpst -u app -e /tmp/init/app-bravo-1/env ./bravo
+27 -2
View File
@@ -12,6 +12,10 @@ def mock_error(subject, message)
end end
end end
def foreman(args)
Foreman::CLI.start(args.split(" "))
end
def mock_exit(&block) def mock_exit(&block)
block.should raise_error(SystemExit) block.should raise_error(SystemExit)
end end
@@ -24,15 +28,21 @@ def write_foreman_config(app)
end end
end end
def write_procfile(procfile="Procfile") def write_procfile(procfile="Procfile", alpha_env="")
File.open(procfile, "w") do |file| File.open(procfile, "w") do |file|
file.puts "alpha: ./alpha" file.puts "alpha: ./alpha" + " #{alpha_env}".rstrip
file.puts "\n" file.puts "\n"
file.puts "bravo:\t./bravo" file.puts "bravo:\t./bravo"
end end
File.expand_path(procfile) File.expand_path(procfile)
end end
def write_env(env=".env")
File.open(env, "w") do |file|
file.puts "FOO=bar"
end
end
def load_export_templates_into_fakefs(type) def load_export_templates_into_fakefs(type)
FakeFS.deactivate! FakeFS.deactivate!
files = Dir[File.expand_path("../../data/export/#{type}/**", __FILE__)].inject({}) do |hash, file| files = Dir[File.expand_path("../../data/export/#{type}/**", __FILE__)].inject({}) do |hash, file|
@@ -53,8 +63,23 @@ def example_export_file(filename)
data data
end 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| RSpec.configure do |config|
config.color_enabled = true config.color_enabled = true
config.order = 'rand'
config.include FakeFS::SpecHelpers config.include FakeFS::SpecHelpers
config.mock_with :rr config.mock_with :rr
end end