Compare commits

...
98 Commits
Author SHA1 Message Date
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
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
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
David Dollar 91edac3197 0.22.0 2011-09-12 20:30:19 -04:00
David Dollar 20e598abcc dont use the runner, not needed 2011-09-12 20:30:01 -04:00
David Dollar ec7f4a480d fix pkg bin 2011-09-12 19:07:25 -04:00
David Dollar 51376058d4 fix procfile to be compatible with non-unix 2011-09-12 19:07:14 -04:00
David Dollar f7542083dd Merge pull request #69 from greinacker/whitespace
Whitespace in Procfile
2011-09-12 11:39:10 -07:00
David Dollar 23124afc7e fix gitignore 2011-09-12 12:55:37 -04:00
David Dollar c948858f45 store objects in s3, list them in readme 2011-09-12 11:11:36 -04:00
David Dollar f358d897fa set up distribution mechanisms 2011-09-12 10:58:35 -04:00
Greg Reinacker ec1b06abb5 update authors list 2011-09-11 17:03:07 -06:00
Greg Reinacker 75d4fc562d accept any whitespace around : in Procfile 2011-09-11 17:01:31 -06:00
David Dollar 1cbb295b0d update authors 2011-09-09 17:06:07 -04:00
David Dollar 60fb125b51 update manual 2011-09-09 16:59:19 -04:00
David Dollar 69d596bb8b add bluepill export to docs 2011-09-09 16:59:09 -04:00
David Dollar cbd5a6344c 0.21.0 2011-09-09 16:59:01 -04:00
David Dollar 4230455859 Merge branch 'master' of github.com:ddollar/foreman 2011-09-09 16:56:32 -04:00
David Dollar b25dfee62d Merge pull request #59 from hunter/bluepill
Bluepill export
2011-09-09 13:56:02 -07:00
David Dollar b73da71c9c update rake 2011-09-09 16:54:37 -04:00
David Dollar 43558758fc Merge pull request #67 from thommay/env-for-upstart
Env for upstart
2011-09-09 13:52:32 -07:00
Thom May 6da8aca609 foreman erb doesn't have the -%> extension enabled. 2011-09-07 15:40:33 +01:00
Thom May 9df93a64cc Export environment to upstart jobs 2011-09-07 15:40:17 +01:00
Thom May 8fd9b753f4 Actually test that the environment is set correctly 2011-09-06 15:59:26 +01:00
Thom May 7fc6d02e7b Read environment at initialisation
This allows us to expose the environment attribute from the engine
object and utilise it to build exported startup files.
2011-09-01 17:26:50 +01:00
Thom May ddcaac8749 Move options into class initialisation 2011-09-01 17:06:37 +01:00
Hunter Nield 9db97abb10 Updates to get Bluepill export working & tweaks to output 2011-08-25 21:17:00 +10:00
David Dollar bda6e30323 0.20.0 2011-08-22 17:27:50 -04:00
David Dollar ffd77312bb Merge pull request #53 from matth/master
Blank line in Procfile breaks Foreman
2011-08-22 14:27:03 -07:00
David Dollar a24c4ce17b Merge pull request #47 from minhajuddin/master
A little convention over configuration love.
2011-08-22 14:23:43 -07:00
David Dollar cf3d7a0b12 Merge pull request #55 from caos/master
Fix for "..../lib/foreman/engine.rb:117:in `eof?': Input/output error - /dev/pts/n (Errno::EIO)" errors
2011-08-22 14:19:40 -07:00
Hunter Nield 21a041527c Added basic support for Bluepill 2011-08-22 21:24:34 +10:00
David Dollar 6ad344d214 consistency 2011-08-18 12:54:17 -04:00
David Dollar 3b8fec463d update manual to mention .env 2011-08-18 12:52:03 -04:00
Mike Javorski 4e015b7436 add Errno::EIO to list of rescued exceptions as underlying pts can close before shutdown is complete
Possibly relates to GitHub Issue #40.
2011-08-11 11:59:51 -07:00
Matt Haynes 2042de7732 Fix bug where blank lines in Procfile break Foreman. 2011-07-28 11:40:31 +01:00
Khaja Minhajuddin 64338c5a09 use a dedicated directory (~/.foreman/templates) to store the templates 2011-06-30 22:23:38 +05:30
Khaja Minhajuddin 8cef71766c tweaked the upstart export code so that it looks for templates in ~/.foreman if no template_root is specified. 2011-06-30 21:50:12 +05:30
David Dollar a2ba079665 0.19.0 2011-06-27 13:17:54 -04:00
David Dollar c8d0dba1cb tweaks to template roots, add testing 2011-06-27 13:17:26 -04:00
Michael van Rooijen e8d2552caa Added the ability to use template configuration files using the '--template [-t]' command line option. This allows you to create a directory on the file system containing your configuration files which Foreman will read from instead of the default templates. 2011-06-26 19:02:51 +02:00
David Dollar 927a57f738 Merge pull request #43 from smith/master
RSpec Warning Fix
2011-06-20 20:35:55 -07:00
Nathan L Smith f65538c1b1 update rspec to 2.6 2011-06-20 21:58:16 -05:00
Nathan L Smith 470c8043af fix rspec warnings 2011-06-20 21:44:35 -05:00
David Dollar 3e69b27f5f Merge pull request #34 from nz/empty-foreman-file
Gracefully handle the 'garbage in' of an empty .foreman file.
2011-06-03 13:51:29 -07:00
Nick Zadrozny f4b1e3701b Gracefully handle the 'garbage in' of an empty .foreman file. 2011-06-03 15:48:13 -05:00
David Dollar fd234b8044 0.18.0 2011-06-03 01:32:14 -04:00
David Dollar f2f09554c8 correct shutdown signals
processes are sent SIGTERM followed 3 seconds later by SIGKILL
2011-06-03 01:31:51 -04:00
David Dollar 6c465b4ef1 remove debug print 2011-06-03 01:31:08 -04:00
55 changed files with 1102 additions and 245 deletions
+7 -7
View File
@@ -1,8 +1,8 @@
.bundle /.bundle
coverage /coverage
example/log/* /example/log/*
man/*.html /man/*.html
man/*.markdown /man/*.markdown
pkg /pkg
tags /tags
+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]
+12
View File
@@ -1,3 +1,15 @@
source "http://rubygems.org" source "http://rubygems.org"
gemspec gemspec
group :development do
gem 'parka'
gem 'rake'
gem 'ronn'
gem 'fakefs', '~> 0.3.2'
gem 'rcov', '~> 0.9.8'
gem 'rr', '~> 1.0.2'
gem 'rspec', '~> 2.6.0'
gem 'aws-s3'
gem "rubyzip"
end
+23 -16
View File
@@ -1,16 +1,21 @@
PATH PATH
remote: . remote: .
specs: specs:
foreman (0.17.0) foreman (0.28.0.pre2)
term-ansicolor (~> 1.0.5) term-ansicolor (~> 1.0.5)
thor (>= 0.13.6) thor (>= 0.13.6)
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
aws-s3 (0.6.2)
builder
mime-types
xml-simple
builder (3.0.0)
crack (0.1.8) crack (0.1.8)
diff-lcs (1.1.2) diff-lcs (1.1.2)
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)
@@ -18,7 +23,7 @@ GEM
crack crack
rest-client rest-client
thor thor
rake (0.8.7) rake (0.9.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)
@@ -28,28 +33,30 @@ 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.0.1) rspec (2.6.0)
rspec-core (~> 2.0.1) rspec-core (~> 2.6.0)
rspec-expectations (~> 2.0.1) rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.0.1) rspec-mocks (~> 2.6.0)
rspec-core (2.0.1) rspec-core (2.6.4)
rspec-expectations (2.0.1) rspec-expectations (2.6.0)
diff-lcs (>= 1.1.2) diff-lcs (~> 1.1.2)
rspec-mocks (2.0.1) rspec-mocks (2.6.0)
rspec-core (~> 2.0.1) rubyzip (0.9.4)
rspec-expectations (~> 2.0.1) term-ansicolor (1.0.7)
term-ansicolor (1.0.5)
thor (0.14.6) thor (0.14.6)
xml-simple (1.0.15)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
fakefs (~> 0.2.1) aws-s3
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.0.0) rspec (~> 2.6.0)
rubyzip
+20 -1
View File
@@ -2,8 +2,18 @@
## Installation ## Installation
* Rubygems
gem install foreman gem install foreman
* OSX
http://assets.foreman.io/foreman/foreman.pkg
* Standalone Tarball
http://assets.foreman.io/foreman/foreman.tgz
## Description ## Description
http://blog.daviddollar.org/2011/05/06/introducing-foreman.html http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
@@ -19,11 +29,20 @@ Created by David Dollar
Patches contributed by: Patches contributed by:
* Adam Wiggins * Adam Wiggins
* clifff
* Dan Peterson * Dan Peterson
* Hunter Nield
* Jay Zeschin * Jay Zeschin
* Keith Rarick * Keith Rarick
* Khaja Minhajuddin
* Matt Haynes
* Michael van Rooijen
* Mike Javorski
* Nathan L Smith
* Nick Zadrozny
* Ricardo Chimal, Jr * Ricardo Chimal, Jr
* Thom May
* clifff
* Greg Reinacker
## License ## License
+133 -2
View File
@@ -13,7 +13,7 @@ task :default => :spec
task :release => :man task :release => :man
desc "Run all specs" desc "Run all specs"
Rspec::Core::RakeTask.new(:spec) do |t| RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = 'spec/**/*_spec.rb' t.pattern = 'spec/**/*_spec.rb'
end end
@@ -22,7 +22,7 @@ task :rcov => "rcov:build" do
%x{ open coverage/index.html } %x{ open coverage/index.html }
end end
Rspec::Core::RakeTask.new("rcov:build") do |t| RSpec::Core::RakeTask.new("rcov:build") do |t|
t.pattern = 'spec/**/*_spec.rb' t.pattern = 'spec/**/*_spec.rb'
t.rcov = true t.rcov = true
t.rcov_opts = [ "--exclude", ".bundle", "--exclude", "spec" ] t.rcov_opts = [ "--exclude", ".bundle", "--exclude", "spec" ]
@@ -54,3 +54,134 @@ task :pages => "man:commit" do
git checkout master git checkout master
} }
end 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
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
+2 -2
View File
@@ -1,2 +1,2 @@
ticker: ./ticker $PORT ticker: ruby ./ticker $PORT
error : ./error error: ruby ./error
+2
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
$stdout.sync = true
puts "will error in 10s" puts "will error in 10s"
sleep 5 sleep 5
raise "Dying" raise "Dying"
+8
View File
@@ -1,5 +1,13 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
$stdout.sync = true
%w( SIGINT SIGTERM ).each do |signal|
trap(signal) do
puts "received #{signal} but i'm ignoring it!"
end
end
while true while true
puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}" puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
sleep 1 sleep 1
+27
View File
@@ -0,0 +1,27 @@
Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "<%= user %>"
app.gid = "<%= user %>"
<% engine.procfile.entries.each do |process| %>
<% 1.upto(concurrency[process.name]) do |num| %>
<% port = engine.port_for(process, num, options[:port]) %>
app.process("<%= process.name %>-<%=num%>") do |process|
process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
process.working_dir = "<%= engine.directory %>"
process.daemonize = true
process.environment = {"PORT" => "<%= port %>"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "<%= app %>-<%= process.name %>"
end
<% end %>
<% end %>
end
+7
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 %>
+1 -1
View File
@@ -2,4 +2,4 @@ start on starting <%= app %>-<%= process.name %>
stop on stopping <%= app %>-<%= process.name %> stop on stopping <%= app %>-<%= process.name %>
respawn respawn
exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>; <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1' exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= env %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
+14
View File
@@ -0,0 +1,14 @@
file pkg("foreman-#{version}.gem") => distribution_files do |t|
sh "gem build foreman.gemspec"
sh "mv foreman-#{version}.gem #{t.name}"
end
task "gem:build" => pkg("foreman-#{version}.gem")
task "gem:clean" do
clean pkg("foreman-#{version}.gem")
end
task "gem:release" => "gem:build" do |t|
sh "parka push -f #{pkg("foreman-#{version}.gem")}"
end
+52
View File
@@ -0,0 +1,52 @@
require "erb"
file pkg("foreman-#{version}.pkg") => distribution_files do |t|
tempdir do |dir|
mkchdir("foreman") do
assemble_distribution
assemble_gems
assemble resource("pkg/foreman"), "bin/foreman", 0755
end
kbytes = %x{ du -ks foreman | cut -f 1 }
num_files = %x{ find foreman | wc -l }
mkdir_p "pkg"
mkdir_p "pkg/Resources"
mkdir_p "pkg/foreman-#{version}.pkg"
dist = File.read(resource("pkg/Distribution.erb"))
dist = ERB.new(dist).result(binding)
File.open("pkg/Distribution", "w") { |f| f.puts dist }
dist = File.read(resource("pkg/PackageInfo.erb"))
dist = ERB.new(dist).result(binding)
File.open("pkg/foreman-#{version}.pkg/PackageInfo", "w") { |f| f.puts dist }
mkdir_p "pkg/foreman-#{version}.pkg/Scripts"
cp resource("pkg/postinstall"), "pkg/foreman-#{version}.pkg/Scripts/postinstall"
chmod 0755, "pkg/foreman-#{version}.pkg/Scripts/postinstall"
sh %{ mkbom -s foreman pkg/foreman-#{version}.pkg/Bom }
Dir.chdir("foreman") do
sh %{ pax -wz -x cpio . > ../pkg/foreman-#{version}.pkg/Payload }
end
sh %{ pkgutil --flatten pkg foreman-#{version}.pkg }
cp_r "foreman-#{version}.pkg", t.name
end
end
task "pkg:build" => pkg("foreman-#{version}.pkg")
task "pkg:clean" do
clean pkg("foreman-#{version}.pkg")
end
task "pkg:release" => "pkg:build" do |t|
store pkg("foreman-#{version}.pkg"), "foreman/foreman-#{version}.pkg"
store pkg("foreman-#{version}.pkg"), "foreman/foreman-beta.pkg" if beta?
store pkg("foreman-#{version}.pkg"), "foreman/foreman.pkg" unless beta?
end
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<installer-script minSpecVersion="1.000000" authoringTool="org.ruby-lang.rake" authoringToolVersion="0.8.7">
<title>Foreman</title>
<options customize="never" allow-external-scripts="no"/>
<domains enable_localSystem="true"/>
<script><![CDATA[
function needs_git() {
return (system.compareVersion(system.version.ProductVersion, "10.7.0") < 0);
}
]]></script>
<choices-outline>
<line choice="git"/>
<line choice="foreman-<%= version %>"/>
</choices-outline>
<choice id="git" title="git" start_selected="false" start_enabled="false" selected="needs_git()" enabled="needs_git()">
<pkg-ref id="git.pkg" />
</choice>
<choice id="foreman-<%= version %>" title="foreman">
<pkg-ref id="io.foreman.installer"/>
</choice>
<pkg-ref id="io.foreman.installer" installKBytes="<%= kbytes %>" version="<%= version %>" auth="Root">#foreman-<%= version %>.pkg</pkg-ref>
</installer-script>
+7
View File
@@ -0,0 +1,7 @@
<pkg-info format-version="2" identifier="io.foreman.installer" version="<%= version %>" install-location="/usr/local/foreman" auth="root">
<payload installKBytes="<%= kbytes %>" numberOfFiles="<%= num_files %>"/>
<scripts>
<postinstall file="./postinstall"/>
</scripts>
</pkg-info>
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/ruby
require "pathname"
bin_file = Pathname.new(__FILE__).realpath
gem_dir = File.expand_path("../../vendor/gems", bin_file)
Dir["#{gem_dir}/**/lib"].each do |libdir|
$:.unshift libdir
end
$:.unshift File.expand_path("../../lib", bin_file)
require "foreman/cli"
Foreman::CLI.start
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env ruby
require "pathname"
bin_file = Pathname.new(__FILE__).realpath
gem_dir = File.expand_path("../vendor/gems", bin_file)
Dir["#{gem_dir}/**/lib"].each do |libdir|
$:.unshift libdir
end
$:.unshift File.expand_path("../lib", bin_file)
require "foreman/cli"
Foreman::CLI.start
+24
View File
@@ -0,0 +1,24 @@
file pkg("foreman-#{version}.tgz") => distribution_files do |t|
tempdir do |dir|
mkchdir("foreman") do
assemble_distribution
assemble_gems
rm_rf "bin"
assemble resource("tgz/foreman"), "foreman", 0755
end
sh "tar czvf #{t.name} foreman"
end
end
task "tgz:build" => pkg("foreman-#{version}.tgz")
task "tgz:clean" do
clean pkg("foreman-#{version}.tgz")
end
task "tgz:release" => "tgz:build" do |t|
store pkg("foreman-#{version}.tgz"), "foreman/foreman-#{version}.tgz"
store pkg("foreman-#{version}.tgz"), "foreman/foreman-beta.tgz" if beta?
store pkg("foreman-#{version}.tgz"), "foreman/foreman.tgz" unless beta?
end
-8
View File
@@ -18,12 +18,4 @@ Gem::Specification.new do |gem|
gem.add_dependency 'term-ansicolor', '~> 1.0.5' gem.add_dependency 'term-ansicolor', '~> 1.0.5'
gem.add_dependency 'thor', '>= 0.13.6' gem.add_dependency 'thor', '>= 0.13.6'
gem.add_development_dependency 'parka'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'ronn'
gem.add_development_dependency 'fakefs', '~> 0.2.1'
gem.add_development_dependency 'rcov', '~> 0.9.8'
gem.add_development_dependency 'rr', '~> 1.0.2'
gem.add_development_dependency 'rspec', '~> 2.0.0'
end end
+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
+11 -13
View File
@@ -8,28 +8,25 @@ class Foreman::CLI < Thor
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile" class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
desc "start [PROCESS]", "Start the application, or a specific process" desc "start", "Start the application"
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) def start
check_procfile! check_procfile!
engine.start
if process
engine.execute(process, options)
else
engine.start(options)
end
end 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 :concurrency, :type => :string, :aliases => "-c", method_option :concurrency, :type => :string, :aliases => "-c",
:banner => '"alpha=5,bar=3"' :banner => '"alpha=5,bar=3"'
@@ -39,6 +36,8 @@ class Foreman::CLI < Thor
formatter = case format formatter = case format
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 "runit" then Foreman::Export::Runit
else error "Unknown export format: #{format}." else error "Unknown export format: #{format}."
end end
@@ -51,9 +50,8 @@ 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 end
private ###################################################################### private ######################################################################
@@ -63,7 +61,7 @@ private ######################################################################
end end
def engine def engine
@engine ||= Foreman::Engine.new(procfile) @engine ||= Foreman::Engine.new(procfile, options)
end end
def procfile def procfile
@@ -86,7 +84,7 @@ private ######################################################################
def options def options
original_options = super original_options = super
return original_options unless File.exists?(".foreman") return original_options unless File.exists?(".foreman")
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
+9
View File
@@ -0,0 +1,9 @@
module Foreman
module Distribution
def self.files
Dir[File.expand_path("../../../{bin,data,lib}/**/*", __FILE__)].select do |file|
File.file?(file)
end
end
end
end
+124 -125
View File
@@ -1,8 +1,10 @@
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 "term/ansicolor" require "term/ansicolor"
require "fileutils" require "fileutils"
@@ -10,138 +12,124 @@ class Foreman::Engine
attr_reader :procfile attr_reader :procfile
attr_reader :directory attr_reader :directory
attr_reader :options
extend Term::ANSIColor extend Term::ANSIColor
COLORS = [ cyan, yellow, green, magenta, red ] COLORS = [ cyan, yellow, green, magenta, red ]
def initialize(procfile) def initialize(procfile, options={})
@procfile = read_procfile(procfile) @procfile = Foreman::Procfile.new(procfile)
@directory = File.expand_path(File.dirname(procfile)) @directory = File.expand_path(File.dirname(procfile))
@options = options
@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 if line.strip == ""
name, command = line.split(/ *: +/, 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 end
def process_order def start
processes
@order.uniq
end
def processes_in_order
process_order.map do |name|
[name, processes[name]]
end
end
def start(options={})
environment = read_environment(options[:env])
proctitle "ruby: foreman master" proctitle "ruby: foreman master"
termtitle "#{File.basename(@directory)} - foreman"
processes_in_order.each do |name, process| trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
fork process, options, environment trap("INT") { puts "SIGINT received"; terminate_gracefully }
end
trap("TERM") { puts "SIGTERM received"; kill_all("TERM") }
trap("INT") { puts "SIGINT received"; kill_all("TERM") }
watch_for_termination
end
def execute(name, options={})
environment = read_environment(options[:env])
fork processes[name], options, environment
trap("TERM") { puts "SIGTERM received"; kill_all("TERM") }
trap("INT") { puts "SIGINT received"; kill_all("TERM") }
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, options={}, environment={}) 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]), environment) reader, writer = IO.pipe
entry.spawn(concurrency[entry.name], writer, @directory, @environment, base_port).each do |process|
running_processes[process.pid] = process
readers[process] = reader
end
end end
end end
def fork_individual(process, num, port, environment) 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 end
def run(process) def kill_all(signal="SIGTERM")
proctitle "ruby: foreman #{process.name}" running_processes.each do |pid, process|
Process.kill(signal, pid) rescue Errno::ESRCH
end
end
begin def terminate_gracefully
Dir.chdir directory do info "sending SIGTERM to all processes"
command = process.command kill_all "SIGTERM"
Timeout.timeout(5) { Process.waitall }
rescue Timeout::Error
info "sending SIGKILL to all processes"
kill_all "SIGKILL"
end
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid| def watch_for_output
until stdin.eof? Thread.new do
info stdin.gets, process 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
end end
end rescue Exception => ex
rescue PTY::ChildExited, Interrupt puts ex.message
begin puts ex.backtrace
info "process exiting", process
rescue Interrupt
end end
end end
end end
def kill_all(signal="TERM") def watch_for_termination
info "terminating" pid, status = Process.wait2
running_processes.each do |pid, process| process = running_processes.delete(pid)
info "killing #{process.name} in pid #{pid}" info "process terminated", process.name
Process.kill(signal, pid) terminate_gracefully
end kill_all
rescue Errno::ECHILD
end end
def info(message, process=nil) def info(message, name="system", color=Term::ANSIColor.white)
print process.color if process print color
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | " 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 end
def print(message=nil)
@output_mutex.synchronize do
$stdout.print message
end
end
def puts(message=nil)
@output_mutex.synchronize do
$stdout.puts message
end
end
def error(message) def error(message)
puts "ERROR: #{message}" puts "ERROR: #{message}"
exit 1 exit 1
@@ -149,72 +137,83 @@ 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
kill_all
Process.waitall
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 >= COLORS.length ? "" : 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!
@environment.each { |k,v| ENV[k] = v }
end
end end
include Env
extend Env
end end
+2
View File
@@ -7,3 +7,5 @@ end
require "foreman/export/base" require "foreman/export/base"
require "foreman/export/inittab" require "foreman/export/inittab"
require "foreman/export/upstart" require "foreman/export/upstart"
require "foreman/export/bluepill"
require "foreman/export/runit"
+8 -2
View File
@@ -23,8 +23,14 @@ private ######################################################################
puts "[foreman export] %s" % message puts "[foreman export] %s" % message
end end
def export_template(name) def export_template(exporter, file, template_root)
File.read(File.expand_path("../../../../data/export/#{name}", __FILE__)) 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))
File.read(file_path)
else
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
end
end end
def write_file(filename, contents) def write_file(filename, contents)
+28
View File
@@ -0,0 +1,28 @@
require "erb"
require "foreman/export"
class Foreman::Export::Bluepill < Foreman::Export::Base
def export(location, options={})
error("Must specify a location") unless location
FileUtils.mkdir_p location
app = options[:app] || File.basename(engine.directory)
user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template]
Dir["#{location}/#{app}.pill"].each do |file|
say "cleaning up: #{file}"
FileUtils.rm(file)
end
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
master_template = export_template("bluepill", "master.pill.erb", template_root)
master_config = ERB.new(master_template).result(binding)
write_file "#{location}/#{app}.pill", master_config
end
end
+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])
+60
View File
@@ -0,0 +1,60 @@
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
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
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
+6 -4
View File
@@ -11,6 +11,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
app = options[:app] || File.basename(engine.directory) app = options[:app] || File.basename(engine.directory)
user = options[:user] || app user = options[:user] || app
log_root = options[:log] || "/var/log/#{app}" log_root = options[:log] || "/var/log/#{app}"
template_root = options[:template]
Dir["#{location}/#{app}*.conf"].each do |file| Dir["#{location}/#{app}*.conf"].each do |file|
say "cleaning up: #{file}" say "cleaning up: #{file}"
@@ -19,14 +20,15 @@ class Foreman::Export::Upstart < Foreman::Export::Base
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency]) concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
master_template = export_template("upstart/master.conf.erb") master_template = export_template("upstart", "master.conf.erb", template_root)
master_config = ERB.new(master_template).result(binding) master_config = ERB.new(master_template).result(binding)
write_file "#{location}/#{app}.conf", master_config write_file "#{location}/#{app}.conf", master_config
process_template = export_template("upstart/process.conf.erb") process_template = export_template("upstart", "process.conf.erb", template_root)
engine.processes.values.each do |process| engine.procfile.entries.each do |process|
process_master_template = export_template("upstart/process_master.conf.erb") next if (conc = concurrency[process.name]) < 1
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
process_master_config = ERB.new(process_master_template).result(binding) 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
+51 -6
View File
@@ -2,13 +2,58 @@ 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 run_process(command, pipe)
io = IO.popen([Foreman.runner, replace_command_env(command)], "w+")
@pid = io.pid
trap("SIGTERM") { "got sigterm for %d" % @pid }
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
+1 -1
View File
@@ -3,7 +3,7 @@ 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| pairs.inject(Hash.new(1)) do |hash, pair|
process, amount = pair.split("=") process, amount = pair.split("=")
+1 -1
View File
@@ -1,5 +1,5 @@
module Foreman module Foreman
VERSION = "0.17.0" VERSION = "0.28.0.pre2"
end end
+24 -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" "May 2011" "Foreman 0.15.0" "Foreman Manual" .TH "FOREMAN" "1" "November 2011" "Foreman 0.26.0" "Foreman Manual"
. .
.SH "NAME" .SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications \fBforeman\fR \- manage Procfile\-based applications
@@ -69,7 +69,7 @@ These options control all modes of foreman\'s operation\.
. .
.TP .TP
\fB\-e\fR, \fB\-\-env\fR \fB\-e\fR, \fB\-\-env\fR
Specify a file containing the environment that should be set up for each child process\. The file should be key/value pairs separated by \fB=\fR, with one key/value pair per line\. 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
@@ -79,9 +79,15 @@ Specify an alternate location for the application\'s Procfile\. This file\'s con
foreman currently supports the following output formats: foreman currently supports the following output formats:
. .
.IP "\(bu" 4 .IP "\(bu" 4
bluepill
.
.IP "\(bu" 4
inittab inittab
. .
.IP "\(bu" 4 .IP "\(bu" 4
runit
.
.IP "\(bu" 4
upstart upstart
. .
.IP "" 0 .IP "" 0
@@ -129,7 +135,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
. .
@@ -141,6 +147,20 @@ $ foreman check
. .
.IP "" 0 .IP "" 0
. .
.SH "ENVIRONMENT"
If a \fB\.env\fR file exists in the current directory, the default environment will be read from it\. This file should contain key/value pairs, separated by \fB=\fR, with one key/value pair per line\.
.
.IP "" 4
.
.nf
FOO=bar
BAZ=qux
.
.fi
.
.IP "" 0
.
.SH "DEFAULT OPTIONS" .SH "DEFAULT OPTIONS"
If a \fB\.foreman\fR file exists in the current directory, default options will be read from it\. This file should be in YAML format with the long option name as keys\. Example: If a \fB\.foreman\fR file exists in the current directory, default options will be read from it\. This file should be in YAML format with the long option name as keys\. Example:
. .
@@ -148,7 +168,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
+18 -5
View File
@@ -67,9 +67,8 @@ 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.
* `-e`, `--env`: * `-e`, `--env`:
Specify a file containing the environment that should be set up for each Specify an alternate environment file. You can specify more than one
child process. The file should be key/value pairs separated by `=`, with file by using: `--env file1,file2`.
one key/value pair per line.
* `-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
@@ -80,8 +79,12 @@ These options control all modes of foreman's operation.
foreman currently supports the following output formats: foreman currently supports the following output formats:
* bluepill
* inittab * inittab
* runit
* upstart * upstart
## INITTAB EXPORT ## INITTAB EXPORT
@@ -112,17 +115,27 @@ 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
## ENVIRONMENT
If a `.env` file exists in the current directory, the default environment will
be read from it. This file should contain key/value pairs, separated by `=`, with
one key/value pair per line.
FOO=bar
BAZ=qux
## DEFAULT OPTIONS ## DEFAULT OPTIONS
If a `.foreman` file exists in the current directory, default options will 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
+10 -1
View File
@@ -19,13 +19,22 @@ describe "Foreman::CLI" do
it "runs successfully" do it "runs successfully" do
dont_allow(subject).error dont_allow(subject).error
mock.instance_of(Foreman::Engine).start({}) mock.instance_of(Foreman::Engine).start
subject.start subject.start
end end
end end
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
+36 -39
View File
@@ -2,7 +2,7 @@ require "spec_helper"
require "foreman/engine" require "foreman/engine"
describe "Foreman::Engine" do describe "Foreman::Engine" do
subject { Foreman::Engine.new("Procfile") } subject { Foreman::Engine.new("Procfile", {}) }
describe "initialize" do describe "initialize" do
describe "without an existing Procfile" do describe "without an existing Procfile" do
@@ -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,28 +24,21 @@ 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
it "handles concurrency" do it "handles concurrency" do
write_procfile write_procfile
mock(subject).fork_individual(subject.processes["alpha"], 1, 5000, {}) engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
mock(subject).fork_individual(subject.processes["alpha"], 2, 5001, {}) mock.instance_of(Foreman::Process).run_process("./alpha", is_a(IO)).twice
mock(subject).fork_individual(subject.processes["bravo"], 1, 5100, {}) mock.instance_of(Foreman::Process).run_process("./bravo", is_a(IO))
mock(subject).watch_for_termination mock(engine).watch_for_output
subject.start(:concurrency => "alpha=2") mock(engine).watch_for_termination
end engine.start
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
end end
@@ -66,24 +46,41 @@ describe "Foreman::Engine" do
before(:each) do before(:each) do
write_procfile write_procfile
stub(Process).fork stub(Process).fork
stub(subject).info
mock(subject).watch_for_termination
end end
it "should read if specified" do it "should read if specified" do
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") } File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
subject.execute("alpha", :env => "/tmp/env") engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == {"FOO"=>"baz"}
engine.start
end
it "should read more than one if specified" do
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
stub(engine).info
mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
engine.start
end end
it "should fail if specified and doesnt exist" do it "should fail if specified and doesnt exist" do
mock(subject).error("No such file: /tmp/env") mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
subject.execute("alpha", :env => "/tmp/env") engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
end end
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") }
mock(subject).fork_individual(anything, anything, anything, { "FOO" => "qoo" }) engine = Foreman::Engine.new("Procfile")
subject.execute("bravo") mock(engine).spawn_processes
mock(engine).watch_for_termination
engine.environment.should == {"FOO"=>"qoo"}
engine.start
end end
end end
end end
+19
View File
@@ -0,0 +1,19 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/bluepill"
require "tmpdir"
describe Foreman::Export::Bluepill do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:engine) { Foreman::Engine.new(procfile) }
let(:bluepill) { Foreman::Export::Bluepill.new(engine) }
before(:each) { load_export_templates_into_fakefs("bluepill") }
before(:each) { stub(bluepill).say }
it "exports to the filesystem" do
bluepill.export("/tmp/init", :concurrency => "alpha=2")
File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill")
end
end
+35
View File
@@ -0,0 +1,35 @@
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 }
it "exports to the filesystem" do
FileUtils.mkdir_p('/tmp/init')
runit.export('/tmp/init', :concurrency => 'alpha=2')
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
+32 -2
View File
@@ -12,8 +12,7 @@ describe Foreman::Export::Upstart do
before(:each) { stub(upstart).say } before(:each) { stub(upstart).say }
it "exports to the filesystem" do it "exports to the filesystem" do
upstart.export("/tmp/init") upstart.export("/tmp/init", :concurrency => "alpha=2")
p [:d, Dir["/tmp/init/**"]]
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")
@@ -22,4 +21,35 @@ describe Foreman::Export::Upstart do
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
context "with alternate templates" do
let(:template_root) { "/tmp/alternate" }
before do
FileUtils.mkdir_p template_root
File.open("#{template_root}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
end
it "can export with alternate template files" do
upstart.export("/tmp/init", :template => template_root)
File.read("/tmp/init/app.conf").should == "alternate_template\n"
end
end
context "with alternate templates from home dir" do
let(:default_template_root) {File.expand_path("~/.foreman/templates")}
before do
FileUtils.mkdir_p default_template_root
File.open("#{default_template_root}/master.conf.erb", "w") { |f| f.puts "default_alternate_template" }
end
it "can export with alternate template files" do
upstart.export("/tmp/init")
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
end
end
end end
+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
+65
View File
@@ -0,0 +1,65 @@
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
app.process("bravo-1") do |process|
process.start_command = "./bravo"
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT" => "5100"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
process.monitor_children do |children|
children.stop_command "kill -QUIT {{PID}}"
end
process.group = "app-bravo"
end
end
@@ -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
+15 -4
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,14 +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 "bravo: ./bravo" file.puts "\n"
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|
@@ -52,7 +63,7 @@ def example_export_file(filename)
data data
end end
Rspec.configure do |config| RSpec.configure do |config|
config.color_enabled = true config.color_enabled = true
config.include FakeFS::SpecHelpers config.include FakeFS::SpecHelpers
config.mock_with :rr config.mock_with :rr