Compare commits
236 Commits
v0.13.1
...
v0.37.0.pre4
| Author | SHA1 | Date | |
|---|---|---|---|
| a04032454a | |||
| 29c94c785d | |||
| a0a2dd9454 | |||
| 1b701cddf3 | |||
| 4080b3f1f2 | |||
| 84c49ae2b8 | |||
| d54b46806c | |||
| bc1c5e4c74 | |||
| ed4a32518f | |||
| c7167e1c83 | |||
| db93432118 | |||
| 5eada6b7cc | |||
| 5e46b1f43a | |||
| ef723f5831 | |||
| 52e24ef64b | |||
| 0b1daf1927 | |||
| e0d84a56d7 | |||
| 7ee2edcc60 | |||
| d428ac5356 | |||
| 5351b49fee | |||
| 6ebe76d8c1 | |||
| 7c43c672c9 | |||
| 41d9050ae3 | |||
| 09e9cefa3a | |||
| 85efe5c1ba | |||
| c21634c04e | |||
| 6d4f3476f1 | |||
| 724812d6e3 | |||
| ce6ec4848d | |||
| a37a097f73 | |||
| f28725bdac | |||
| 51eee011ce | |||
| 6c8c848f54 | |||
| f60c4cb767 | |||
| 4ad49cb058 | |||
| e993af2b20 | |||
| d7000bccfa | |||
| 2995a605b4 | |||
| ef280e802d | |||
| 8a9001842c | |||
| 8a8d31eb43 | |||
| 3e98170878 | |||
| 3d84de3062 | |||
| 9cc0afca49 | |||
| 7a25d3ac5a | |||
| db1a5df354 | |||
| e137596ce0 | |||
| c9042c5aae | |||
| 550adc8070 | |||
| fbdde3e62a | |||
| e161ecb630 | |||
| 853a88dfbf | |||
| b4cab08327 | |||
| ade0005a92 | |||
| 241b91a0d5 | |||
| 047f106d48 | |||
| df043e60d8 | |||
| 158c184f8c | |||
| 2ed1fe8d44 | |||
| a008886bd0 | |||
| c62f892ff6 | |||
| d885e019b3 | |||
| cfd337b44d | |||
| e76f3533dc | |||
| 1485eeb859 | |||
| e0b5928e88 | |||
| a73dce5405 | |||
| 2abddb42b3 | |||
| d961a32cfe | |||
| 2bfc065c1d | |||
| fbe3d4ec69 | |||
| 631187e0d8 | |||
| 92d1a4d367 | |||
| f4123f4ae1 | |||
| d4c2332c59 | |||
| e257fc89c1 | |||
| a278755ae4 | |||
| 3367a060a7 | |||
| ac7e0743ac | |||
| e574880814 | |||
| 7132cacbf6 | |||
| c1f279aa6f | |||
| 34cfe9ef9d | |||
| 79fc3b8029 | |||
| 91140638e1 | |||
| 48cc60c30f | |||
| 533139ea9f | |||
| 86e2056a24 | |||
| ab29963ee4 | |||
| cf269c39da | |||
| 76cd2e794b | |||
| 83748cb538 | |||
| d2c9ce0f34 | |||
| 98337c92e1 | |||
| 33d738b3f8 | |||
| 9432989fbe | |||
| 66b1483a75 | |||
| 64bd4db128 | |||
| b561555f3a | |||
| baa7b7685c | |||
| cfa6e6f259 | |||
| a34bc59721 | |||
| 07e8ca4a4b | |||
| 342d30bbb8 | |||
| 268dd6240e | |||
| 9e60b3e1a4 | |||
| 1c6285f8af | |||
| 5de1bd18ac | |||
| fff15bc627 | |||
| a66157d611 | |||
| fcfa913fb0 | |||
| fc438472f9 | |||
| fc95936327 | |||
| 0c27f78d46 | |||
| 356c61f471 | |||
| dcff4da220 | |||
| 888520ee99 | |||
| c7b6b334fd | |||
| f476920a05 | |||
| 5436b68cf1 | |||
| c9411cd2b1 | |||
| 6e95d1ce94 | |||
| c5548a345e | |||
| f668b87660 | |||
| 914a1ee958 | |||
| e1c2946718 | |||
| 6160246da0 | |||
| 2e8030dbd4 | |||
| 58e4cdafd7 | |||
| 44a5dff724 | |||
| e33921f083 | |||
| 79cf541ebe | |||
| 8bc8cb4b2e | |||
| 39ace26ae1 | |||
| c383359136 | |||
| a5e094353c | |||
| 12720b4c12 | |||
| 1c732a4658 | |||
| 8908a66e90 | |||
| f63c0b0ca0 | |||
| 676d3ff8d1 | |||
| 615aada17e | |||
| 2e1d1c7c15 | |||
| bf832ffd9f | |||
| b9bfede48a | |||
| bed8323029 | |||
| f6ef5a5b4f | |||
| f3c1e76860 | |||
| 33aa1efc90 | |||
| caa688cdc6 | |||
| c6a410b664 | |||
| 02c8d2cb10 | |||
| ada41ce311 | |||
| 8f1c752a77 | |||
| ddf25fe0ea | |||
| 9dac91a624 | |||
| cdaeb646ac | |||
| 86e4251840 | |||
| ba18f7e589 | |||
| be73e8500f | |||
| d26ca669a1 | |||
| a3e758ab6c | |||
| 5dc232a7b1 | |||
| 4191cb7b9c | |||
| 90d4dffb82 | |||
| 823f307abc | |||
| ed44a11e21 | |||
| 5719f4fc72 | |||
| 47008fb921 | |||
| 91edac3197 | |||
| 20e598abcc | |||
| ec7f4a480d | |||
| 51376058d4 | |||
| f7542083dd | |||
| 23124afc7e | |||
| c948858f45 | |||
| f358d897fa | |||
| ec1b06abb5 | |||
| 75d4fc562d | |||
| 1cbb295b0d | |||
| 60fb125b51 | |||
| 69d596bb8b | |||
| cbd5a6344c | |||
| 4230455859 | |||
| b25dfee62d | |||
| b73da71c9c | |||
| 43558758fc | |||
| 6da8aca609 | |||
| 9df93a64cc | |||
| 8fd9b753f4 | |||
| 7fc6d02e7b | |||
| ddcaac8749 | |||
| 9db97abb10 | |||
| bda6e30323 | |||
| ffd77312bb | |||
| a24c4ce17b | |||
| cf3d7a0b12 | |||
| 21a041527c | |||
| 6ad344d214 | |||
| 3b8fec463d | |||
| 4e015b7436 | |||
| 2042de7732 | |||
| 64338c5a09 | |||
| 8cef71766c | |||
| a2ba079665 | |||
| c8d0dba1cb | |||
| e8d2552caa | |||
| 927a57f738 | |||
| f65538c1b1 | |||
| 470c8043af | |||
| 3e69b27f5f | |||
| f4b1e3701b | |||
| fd234b8044 | |||
| f2f09554c8 | |||
| 6c465b4ef1 | |||
| c419f8213b | |||
| 7f61fb61ea | |||
| 577a5c7c5c | |||
| 56940c56d9 | |||
| f308ad886d | |||
| 55375b9bde | |||
| 85fcccffa8 | |||
| 58fc18d015 | |||
| f7c9802ef7 | |||
| 1c00d65f29 | |||
| 9193a675a3 | |||
| 8f0b14810c | |||
| 124e27ed22 | |||
| f34f161899 | |||
| 191581fe85 | |||
| b98d558bed | |||
| 180f63624e | |||
| 13fd1188ad | |||
| 92c2b15785 | |||
| 4a2d7565b7 | |||
| 49720e0458 |
+8
-8
@@ -1,8 +1,8 @@
|
||||
.bundle
|
||||
coverage
|
||||
example/log/*
|
||||
man/*.html
|
||||
man/*.markdown
|
||||
pkg
|
||||
tags
|
||||
|
||||
/.bundle
|
||||
/coverage
|
||||
/example/log/*
|
||||
/man/*.html
|
||||
/man/*.markdown
|
||||
/pkg
|
||||
/tags
|
||||
/vendor
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
script: bundle exec rake spec
|
||||
|
||||
rvm:
|
||||
- 1.8.7
|
||||
- 1.9.2
|
||||
- 1.9.3
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
on_success: always
|
||||
on_failure: always
|
||||
urls:
|
||||
- http://dx-helper.herokuapp.com/travis
|
||||
@@ -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]
|
||||
@@ -1,3 +1,24 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gemspec
|
||||
|
||||
platform :mingw do
|
||||
gem "win32console", "~> 1.3.0"
|
||||
end
|
||||
|
||||
platform :jruby do
|
||||
gem "posix-spawn", "~> 0.3.6"
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'parka'
|
||||
gem 'rake'
|
||||
gem 'ronn'
|
||||
gem 'fakefs', '~> 0.3.2'
|
||||
gem 'rcov', '~> 0.9.8'
|
||||
gem 'rr', '~> 1.0.2'
|
||||
gem 'rspec', '~> 2.0'
|
||||
gem 'ZenTest'
|
||||
gem 'aws-s3'
|
||||
gem "rubyzip"
|
||||
end
|
||||
|
||||
+35
-18
@@ -1,25 +1,34 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
foreman (0.13.1)
|
||||
term-ansicolor (~> 1.0.5)
|
||||
foreman (0.37.0.pre4)
|
||||
term-ansicolor (~> 1.0.7)
|
||||
thor (>= 0.13.6)
|
||||
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
ZenTest (4.6.2)
|
||||
aws-s3 (0.6.2)
|
||||
builder
|
||||
mime-types
|
||||
xml-simple
|
||||
builder (3.0.0)
|
||||
crack (0.1.8)
|
||||
diff-lcs (1.1.2)
|
||||
fakefs (0.2.1)
|
||||
diff-lcs (1.1.3)
|
||||
fakefs (0.3.2)
|
||||
hpricot (0.8.2)
|
||||
hpricot (0.8.2-java)
|
||||
mime-types (1.16)
|
||||
mustache (0.11.2)
|
||||
parka (0.6.2)
|
||||
crack
|
||||
rest-client
|
||||
thor
|
||||
rake (0.8.7)
|
||||
posix-spawn (0.3.6)
|
||||
rake (0.9.2.2)
|
||||
rcov (0.9.8)
|
||||
rcov (0.9.8-java)
|
||||
rdiscount (1.6.5)
|
||||
rest-client (1.6.1)
|
||||
mime-types (>= 1.16)
|
||||
@@ -28,28 +37,36 @@ GEM
|
||||
mustache (>= 0.7.0)
|
||||
rdiscount (>= 1.5.8)
|
||||
rr (1.0.2)
|
||||
rspec (2.0.1)
|
||||
rspec-core (~> 2.0.1)
|
||||
rspec-expectations (~> 2.0.1)
|
||||
rspec-mocks (~> 2.0.1)
|
||||
rspec-core (2.0.1)
|
||||
rspec-expectations (2.0.1)
|
||||
diff-lcs (>= 1.1.2)
|
||||
rspec-mocks (2.0.1)
|
||||
rspec-core (~> 2.0.1)
|
||||
rspec-expectations (~> 2.0.1)
|
||||
term-ansicolor (1.0.5)
|
||||
rspec (2.8.0)
|
||||
rspec-core (~> 2.8.0)
|
||||
rspec-expectations (~> 2.8.0)
|
||||
rspec-mocks (~> 2.8.0)
|
||||
rspec-core (2.8.0)
|
||||
rspec-expectations (2.8.0)
|
||||
diff-lcs (~> 1.1.2)
|
||||
rspec-mocks (2.8.0)
|
||||
rubyzip (0.9.4)
|
||||
term-ansicolor (1.0.7)
|
||||
thor (0.14.6)
|
||||
win32console (1.3.0-x86-mingw32)
|
||||
xml-simple (1.0.15)
|
||||
|
||||
PLATFORMS
|
||||
java
|
||||
ruby
|
||||
x86-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
fakefs (~> 0.2.1)
|
||||
ZenTest
|
||||
aws-s3
|
||||
fakefs (~> 0.3.2)
|
||||
foreman!
|
||||
parka
|
||||
posix-spawn (~> 0.3.6)
|
||||
rake
|
||||
rcov (~> 0.9.8)
|
||||
ronn
|
||||
rr (~> 1.0.2)
|
||||
rspec (~> 2.0.0)
|
||||
rspec (~> 2.0)
|
||||
rubyzip
|
||||
win32console (~> 1.3.0)
|
||||
|
||||
+32
-2
@@ -1,8 +1,38 @@
|
||||
# Foreman
|
||||
|
||||
## Manual
|
||||
Manage Procfile-based applications
|
||||
|
||||
See the [man page](http://ddollar.github.com/foreman) for usage.
|
||||
<table>
|
||||
<tr>
|
||||
<th>If you have...</th>
|
||||
<th>Install with...</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ruby (MRI, JRuby, Windows)</td>
|
||||
<td><pre>$ gem install foreman</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mac OS X</td>
|
||||
<td><a href="http://assets.foreman.io/foreman/foreman.pkg">foreman.pkg</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Getting Started
|
||||
|
||||
* http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
|
||||
|
||||
## Documentation
|
||||
|
||||
* [man page](http://ddollar.github.com/foreman)
|
||||
* [wiki](http://github.com/ddollar/foreman/wiki)
|
||||
|
||||
## Authors
|
||||
|
||||
#### Created and maintained by
|
||||
David Dollar
|
||||
|
||||
#### Patches contributed by
|
||||
Adam Wiggins, Chris Continanza, Chris Lowder, Craig R Webster, Dan Farina, Dan Peterson, David Dollar, Fletcher Nichol, Gabriel Burt, Gamaliel Toro, Greg Reinacker, Hugues Le Gendre, Hunter Nield, Iain Hecker, Jay Zeschin, Keith Rarick, Khaja Minhajuddin, Lincoln Stoll, Marcos Muino Garcia, Mark McGranaghan, Matt Griffin, Matt Haynes, Matthijs Langenberg, Michael Dwan, Michael van Rooijen, Mike Javorski, Nathan Broadbent, Nathan L Smith, Nick Zadrozny, Phil Hagelberg, Ricardo Chimal, Jr, Thom May, Tom Ward, brainopia, clifff, jc00ke
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
require "rubygems"
|
||||
require "bundler"
|
||||
Bundler.setup
|
||||
|
||||
require "rake"
|
||||
require "rspec"
|
||||
require "rspec/core/rake_task"
|
||||
@@ -13,7 +9,7 @@ task :default => :spec
|
||||
task :release => :man
|
||||
|
||||
desc "Run all specs"
|
||||
Rspec::Core::RakeTask.new(:spec) do |t|
|
||||
RSpec::Core::RakeTask.new(:spec) do |t|
|
||||
t.pattern = 'spec/**/*_spec.rb'
|
||||
end
|
||||
|
||||
@@ -22,7 +18,7 @@ task :rcov => "rcov:build" do
|
||||
%x{ open coverage/index.html }
|
||||
end
|
||||
|
||||
Rspec::Core::RakeTask.new("rcov:build") do |t|
|
||||
RSpec::Core::RakeTask.new("rcov:build") do |t|
|
||||
t.pattern = 'spec/**/*_spec.rb'
|
||||
t.rcov = true
|
||||
t.rcov_opts = [ "--exclude", ".bundle", "--exclude", "spec" ]
|
||||
@@ -54,3 +50,143 @@ task :pages => "man:commit" do
|
||||
git checkout master
|
||||
}
|
||||
end
|
||||
|
||||
task :authors do
|
||||
authors = %x{ git log --pretty=format:"%an" | sort -u }.split("\n")
|
||||
puts authors.join(", ")
|
||||
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(type=nil)
|
||||
require "foreman/distribution"
|
||||
base_files = Foreman::Distribution.files
|
||||
type_files = type ?
|
||||
Dir[File.expand_path("../dist/resources/#{type}/**/*", __FILE__)] :
|
||||
[]
|
||||
base_files.concat(type_files)
|
||||
end
|
||||
|
||||
def mkchdir(dir)
|
||||
FileUtils.mkdir_p(dir)
|
||||
Dir.chdir(dir) do |dir|
|
||||
yield(File.expand_path(dir))
|
||||
end
|
||||
end
|
||||
|
||||
def pkg(filename)
|
||||
File.expand_path("../pkg/#{filename}", __FILE__)
|
||||
end
|
||||
|
||||
def project_root
|
||||
File.dirname(__FILE__)
|
||||
end
|
||||
|
||||
def resource(name)
|
||||
File.expand_path("../dist/resources/#{name}", __FILE__)
|
||||
end
|
||||
|
||||
def s3_connect
|
||||
return if @s3_connected
|
||||
|
||||
require "aws/s3"
|
||||
|
||||
unless ENV["DAVID_RELEASE_ACCESS"] && ENV["DAVID_RELEASE_SECRET"]
|
||||
puts "please set DAVID_RELEASE_ACCESS and DAVID_RELEASE_SECRET in your environment"
|
||||
exit 1
|
||||
end
|
||||
|
||||
AWS::S3::Base.establish_connection!(
|
||||
:access_key_id => ENV["DAVID_RELEASE_ACCESS"],
|
||||
:secret_access_key => ENV["DAVID_RELEASE_SECRET"]
|
||||
)
|
||||
|
||||
@s3_connected = true
|
||||
end
|
||||
|
||||
def store(package_file, filename, bucket="assets.foreman.io")
|
||||
s3_connect
|
||||
puts "storing: #{filename}"
|
||||
AWS::S3::S3Object.store(filename, File.open(package_file), bucket, :access => :public_read)
|
||||
end
|
||||
|
||||
def tempdir
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir(dir) do
|
||||
yield(dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def version
|
||||
require "foreman/version"
|
||||
Foreman::VERSION
|
||||
end
|
||||
|
||||
Dir[File.expand_path("../dist/**/*.rake", __FILE__)].each do |rake|
|
||||
import rake
|
||||
end
|
||||
|
||||
task :changelog do
|
||||
timestamp = Time.now.utc.strftime('%m/%d/%Y')
|
||||
sha = `git log | head -1`.split(' ').last
|
||||
changelog = ["#{version} #{timestamp} #{sha}"]
|
||||
changelog << ('=' * changelog[0].length)
|
||||
changelog << ''
|
||||
|
||||
last_sha = `cat Changelog | head -1`.split(' ').last
|
||||
shortlog = `git log #{last_sha}..HEAD --pretty=format:'%s [%an]'`
|
||||
changelog << shortlog.split("\n")
|
||||
changelog.concat ['', '', '']
|
||||
|
||||
old_changelog = File.read('Changelog')
|
||||
File.open('Changelog', 'w') do |file|
|
||||
file.write(changelog.join("\n"))
|
||||
file.write(old_changelog)
|
||||
end
|
||||
end
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
#/ Usage: runner [-d <dir>] <command>
|
||||
#/
|
||||
#/ Run a command with exec, optionally changing directory first
|
||||
|
||||
set -e
|
||||
|
||||
error() {
|
||||
echo $@ >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat $0 | grep '^#/' | cut -c4-
|
||||
exit
|
||||
}
|
||||
|
||||
while getopts ":hd:" OPT; do
|
||||
case $OPT in
|
||||
d) cd $OPTARG ;;
|
||||
h) usage ;;
|
||||
\?) error "invalid option: -$OPTARG" ;;
|
||||
:) error "option -$OPTARG requires an argument" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND-1))
|
||||
|
||||
command=$1
|
||||
|
||||
if [ "$1" == "" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
exec $1
|
||||
@@ -1,2 +1,2 @@
|
||||
ticker: ./ticker $PORT
|
||||
error : ./error
|
||||
ticker: ruby ./ticker $PORT
|
||||
error: ruby ./error
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
puts "will error in 10s"
|
||||
sleep 5
|
||||
raise "Dying"
|
||||
|
||||
+9
-1
@@ -1,6 +1,14 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
$stdout.sync = true
|
||||
|
||||
%w( SIGINT SIGTERM ).each do |signal|
|
||||
trap(signal) do
|
||||
puts "received #{signal} but i'm ignoring it!"
|
||||
end
|
||||
end
|
||||
|
||||
while true
|
||||
puts "tick: #{ARGV.inspect}"
|
||||
puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
|
||||
sleep 1
|
||||
end
|
||||
|
||||
@@ -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, self.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
|
||||
@@ -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"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd <%= engine.directory %>
|
||||
exec chpst -u <%= user %> -e <%= process_env_directory %> <%= process.command %>
|
||||
@@ -2,5 +2,4 @@ start on starting <%= app %>-<%= process.name %>
|
||||
stop on stopping <%= app %>-<%= process.name %>
|
||||
respawn
|
||||
|
||||
chdir <%= engine.directory %>
|
||||
exec su - <%= user %> -c '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'
|
||||
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
file pkg("/apt-#{version}/foreman-#{version}.deb") => distribution_files("deb") do |t|
|
||||
mkchdir(File.dirname(t.name)) do
|
||||
mkchdir("usr/local/foreman") do
|
||||
assemble_distribution
|
||||
assemble_gems
|
||||
assemble resource("deb/foreman"), "bin/foreman", 0755
|
||||
File.chmod 0755, "bin/runner"
|
||||
end
|
||||
|
||||
assemble resource("deb/control"), "control"
|
||||
assemble resource("deb/postinst"), "postinst"
|
||||
|
||||
sh "tar czvf data.tar.gz usr/local/foreman --owner=root --group=root"
|
||||
sh "tar czvf control.tar.gz control postinst"
|
||||
|
||||
File.open("debian-binary", "w") do |f|
|
||||
f.puts "2.0"
|
||||
end
|
||||
|
||||
deb = File.basename(t.name)
|
||||
|
||||
sh "ar -r #{t.name} debian-binary control.tar.gz data.tar.gz"
|
||||
|
||||
touch "Sources"
|
||||
sh "apt-ftparchive packages . > Packages"
|
||||
sh "gzip -c Packages > Packages.gz"
|
||||
sh "apt-ftparchive release . > Release"
|
||||
sh "gpg -abs -u 0F1B0520 -o Release.gpg Release"
|
||||
end
|
||||
end
|
||||
|
||||
desc "Build a .deb package"
|
||||
task "deb:build" => pkg("/apt-#{version}/foreman-#{version}.deb")
|
||||
|
||||
desc "Remove build artifacts for .deb"
|
||||
task "deb:clean" do
|
||||
clean pkg("foreman-#{version}.deb")
|
||||
FileUtils.rm_rf("pkg/apt-#{version}") if Dir.exists?("pkg/apt-#{version}")
|
||||
end
|
||||
|
||||
desc "Publish .deb to S3."
|
||||
task "deb:release" => "deb:build" do |t|
|
||||
Dir["pkg/apt-#{version}/*"].each do |file|
|
||||
unless File.directory?(file)
|
||||
store file, "apt/#{File.basename(file)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
Vendored
+14
@@ -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
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
file pkg("foreman-#{version}-jruby.gem") => distribution_files do |t|
|
||||
sh "env PLATFORM=java gem build foreman.gemspec"
|
||||
sh "mv foreman-#{version}-java.gem #{t.name}"
|
||||
end
|
||||
|
||||
task "jruby:build" => pkg("foreman-#{version}-jruby.gem")
|
||||
|
||||
task "jruby:clean" do
|
||||
clean pkg("foreman-#{version}-jruby.gem")
|
||||
end
|
||||
|
||||
task "jruby:release" => "jruby:build" do |t|
|
||||
sh "parka push -f #{pkg("foreman-#{version}-jruby.gem")}"
|
||||
end
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
file pkg("foreman-#{version}-mingw32.gem") => distribution_files do |t|
|
||||
sh "env PLATFORM=mingw32 gem build foreman.gemspec"
|
||||
sh "mv foreman-#{version}-mingw32.gem #{t.name}"
|
||||
end
|
||||
|
||||
task "mingw32:build" => pkg("foreman-#{version}-mingw32.gem")
|
||||
|
||||
task "mingw32:clean" do
|
||||
clean pkg("foreman-#{version}-mingw32.gem")
|
||||
end
|
||||
|
||||
task "mingw32:release" => "mingw32:build" do |t|
|
||||
sh "parka push -f #{pkg("foreman-#{version}-mingw32.gem")}"
|
||||
end
|
||||
Vendored
+52
@@ -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
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
Package: foreman
|
||||
Version: <%= version %>
|
||||
Section: main
|
||||
Priority: standard
|
||||
Architecture: all
|
||||
Depends: ruby1.9.1
|
||||
Maintainer: Heroku
|
||||
Description: Manage Procfile-based applications.
|
||||
Foreman is a manager for Procfile-based applications. Its aim is to
|
||||
abstract away the details of the Procfile format, and allow you to
|
||||
either run your application directly or export it to some other
|
||||
process management format.
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env ruby1.9.1
|
||||
|
||||
# resolve bin path, ignoring symlinks
|
||||
require "pathname"
|
||||
bin_file = Pathname.new(__FILE__).realpath
|
||||
|
||||
# add locally vendored gems to libpath
|
||||
gem_dir = File.expand_path("../../vendor/gems", bin_file)
|
||||
Dir["#{gem_dir}/**/lib"].each do |libdir|
|
||||
$:.unshift libdir
|
||||
end
|
||||
|
||||
# add self to libpath
|
||||
$:.unshift File.expand_path("../../lib", bin_file)
|
||||
|
||||
require "foreman/cli"
|
||||
|
||||
Foreman::CLI.start
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1.4.11 (Darwin)
|
||||
|
||||
mQENBE5SfAEBCADLp056ZgfdtAMXLWpEuL9zY+dIHIY5qLQcDmUivjHLVE4l3Bi3
|
||||
Mn570K0W9rfk7fHBPEO2XJEDdjk8Bg6mWTAeGjdfZgZaL+qO9NjqQ5QmVR+vgp7s
|
||||
yxJYlfY+JYTZvl/JiDWGhuPHSPggXILCMf3SpqWMHGPqe/3RAK+CHCNv/94uaoS4
|
||||
vi4HQT+k4sRceiM8WqkSRYSoc7rzdDejZn+InCYFfR56VeSFF4G4I6neZs/q5T9d
|
||||
Ty2i5d0gZLaX/Iqc+3Dy0vDKClc0HUQJ6ajDPuUqKLHFUpqyuwfJij60+C3GMi8K
|
||||
ckRPti31EPFVzq3GPHU+GqA+e9j84WHr4uJ5ABEBAAG0L0hlcm9rdSBSZWxlYXNl
|
||||
IEVuZ2luZWVyaW5nIDxyZWxlYXNlQGhlcm9rdS5jb20+iQE4BBMBAgAiBQJOUnwB
|
||||
AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDJJ+vgDxsFIChECAC9h4Ay
|
||||
Nx4AQFu85cjR9rijyBflPeVqi7Xhzd7IvLg2+kZSexlb2oidj7iVSMy+vy5tG9g9
|
||||
8Az/JqMCVjcZ7ltn60OGU8gIYpJqt6VmH3vfJBxXu/Sm9tym3UCYGVvMAN5Oq6yB
|
||||
HlQkQ8F3p0cW69PmF+fibkgo9RE0EYlBIt2rUHNilTGFS6vXGr5reFFp3/rRHq3k
|
||||
bixnUwFSqNujJgnBKDPwtSYKc4pMpnhuv88xEpLH7vU8NLXQZMitKQguV8XEmcsu
|
||||
43LXlsx5uVr239/XNW+h412gIHFDSzB/YuLWlVUXMfquC96z/wxMqWWZyskDNgr0
|
||||
WDdMgzK6CUfXSqQhuQENBE5SfAEBCADbnGKcXpdVauQpINQLtRnrT0BJIrIo1Yxv
|
||||
LQRb3G7RU+Eq6aHXwk9fSKa6nEv9RsmqiW874yODnr0d/DTUWMHT+jRvPHm1wlbE
|
||||
pGR1aPSo7GgkSUdaT6CVBN3JWZ2kVJGqohNoJMYbfVaWd/kpa/LiMFWzS8LfWT2K
|
||||
xiO2vIh4qBfeRCGR7s8rADCHuHJ0eibADrgqcRfdPrChB1JiYLeTdV4yRmSzJ7TM
|
||||
zWX7OVpGfIFLbCw9NeN65pI9ePs2mSPM7DYkhhKSXWMwJNXFzn1blOGiwAwKb48P
|
||||
a/QpE6TG3PQzbYyTTP0Td1XgKAHcprvbc89a/nAk3a+PJQ/MqvDzABEBAAGJAR8E
|
||||
GAECAAkFAk5SfAECGwwACgkQySfr4A8bBSD4mAgAnCT5WRiDl0259Px9Z9J9Wk8Z
|
||||
SxugDct2Yhzca4aw1Ou4cfaIFCDXzFlBzSJfqk0HoVhp9r2gzEPUCKnSjRDyxaMo
|
||||
wZCUtqigBua+z4NB4AWgeOl/2S06I2ki1K7pfl4piYcHtEThHamnhVPJ2Hi6HsHq
|
||||
mUU+8SxleHE4GCXmKkuvxelUq9jrhHikIkm1RoqFOPb9zV3WRy4YzVHQSYfHmfk0
|
||||
9kXlM/CS0sfNv2UKCX+5e6eFIZv0rdtpp6VEh0tsFmsIClY6Z9MX7bgp8MnUJpyk
|
||||
OeIzOzQgkb4aeT0Whl+EPcTeDZfqIhVBoNXupUanmWNppFcMngxfqG2NGi1vvQ==
|
||||
=aUAq
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman
|
||||
Vendored
+23
@@ -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>
|
||||
|
||||
Vendored
+7
@@ -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>
|
||||
|
||||
Vendored
+15
@@ -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
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
ln -sf /usr/local/foreman/bin/foreman /usr/bin/foreman
|
||||
Vendored
+15
@@ -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
|
||||
Vendored
+24
@@ -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
|
||||
+10
-8
@@ -16,14 +16,16 @@ Gem::Specification.new do |gem|
|
||||
gem.files = Dir["**/*"].select { |d| d =~ %r{^(README|bin/|data/|ext/|lib/|spec/|test/)} }
|
||||
gem.files << "man/foreman.1"
|
||||
|
||||
gem.add_dependency 'term-ansicolor', '~> 1.0.5'
|
||||
gem.add_dependency 'term-ansicolor', '~> 1.0.7'
|
||||
gem.add_dependency 'thor', '>= 0.13.6'
|
||||
|
||||
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'
|
||||
if ENV["PLATFORM"] == "java"
|
||||
gem.add_dependency "posix-spawn", "~> 0.3.6"
|
||||
gem.platform = Gem::Platform.new("java")
|
||||
end
|
||||
|
||||
if ENV["PLATFORM"] == "mingw32"
|
||||
gem.add_dependency "win32console", "~> 1.3.0"
|
||||
gem.platform = Gem::Platform.new("mingw32")
|
||||
end
|
||||
end
|
||||
|
||||
+18
-1
@@ -4,5 +4,22 @@ module Foreman
|
||||
|
||||
class AppDoesNotExist < Exception; end
|
||||
|
||||
end
|
||||
# load contents of env_file into ENV
|
||||
def self.load_env!(env_file = './.env')
|
||||
require 'foreman/engine'
|
||||
Foreman::Engine.load_env!(env_file)
|
||||
end
|
||||
|
||||
def self.runner
|
||||
File.expand_path("../../bin/runner", __FILE__)
|
||||
end
|
||||
|
||||
def self.jruby?
|
||||
defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java"
|
||||
end
|
||||
|
||||
def self.windows?
|
||||
defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
+46
-22
@@ -1,49 +1,62 @@
|
||||
require "foreman"
|
||||
require "foreman/helpers"
|
||||
require "foreman/engine"
|
||||
require "foreman/export"
|
||||
require "thor"
|
||||
require "yaml"
|
||||
|
||||
class Foreman::CLI < Thor
|
||||
include Foreman::Helpers
|
||||
|
||||
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
||||
|
||||
desc "start [PROCESS]", "Start the application, or a specific process"
|
||||
desc "start", "Start the application"
|
||||
|
||||
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
||||
class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
|
||||
|
||||
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
||||
method_option :port, :type => :numeric, :aliases => "-p"
|
||||
method_option :concurrency, :type => :string, :aliases => "-c",
|
||||
:banner => '"alpha=5,bar=3"'
|
||||
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
||||
|
||||
def start(process=nil)
|
||||
check_procfile!
|
||||
|
||||
if process
|
||||
engine.execute(process, options)
|
||||
else
|
||||
engine.start(options)
|
||||
class << self
|
||||
# Hackery. Take the run method away from Thor so that we can redefine it.
|
||||
def is_thor_reserved_word?(word, type)
|
||||
return false if word == 'run'
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def start
|
||||
check_procfile!
|
||||
engine.start
|
||||
end
|
||||
|
||||
desc "export FORMAT LOCATION", "Export the application to another process management format"
|
||||
|
||||
method_option :app, :type => :string, :aliases => "-a"
|
||||
method_option :log, :type => :string, :aliases => "-l"
|
||||
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
||||
method_option :port, :type => :numeric, :aliases => "-p"
|
||||
method_option :user, :type => :string, :aliases => "-u"
|
||||
method_option :template, :type => :string, :aliases => "-t"
|
||||
method_option :concurrency, :type => :string, :aliases => "-c",
|
||||
:banner => '"alpha=5,bar=3"'
|
||||
|
||||
def export(format, location=nil)
|
||||
check_procfile!
|
||||
|
||||
formatter = case format
|
||||
when "inittab" then Foreman::Export::Inittab
|
||||
when "upstart" then Foreman::Export::Upstart
|
||||
else error "Unknown export format: #{format}."
|
||||
begin
|
||||
require "foreman/export/#{ format.tr('-', '_') }"
|
||||
classy_format = classify(format)
|
||||
formatter = constantize("Foreman::Export::#{ classy_format }")
|
||||
rescue NameError => ex
|
||||
error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })."
|
||||
rescue LoadError => ex
|
||||
error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')."
|
||||
end
|
||||
|
||||
formatter.new(engine).export(location, options)
|
||||
|
||||
formatter.new(location, engine, options).export
|
||||
rescue Foreman::Export::Exception => ex
|
||||
error ex.message
|
||||
end
|
||||
@@ -51,9 +64,21 @@ class Foreman::CLI < Thor
|
||||
desc "check", "Validate your application's Procfile"
|
||||
|
||||
def check
|
||||
processes = engine.processes_in_order.map { |p| p.first }
|
||||
error "no processes defined" unless processes.length > 0
|
||||
display "valid procfile detected (#{processes.join(', ')})"
|
||||
error "no processes defined" unless engine.procfile.entries.length > 0
|
||||
display "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
||||
end
|
||||
|
||||
desc "run COMMAND", "Run a command using your application's environment"
|
||||
|
||||
def run(*args)
|
||||
engine.apply_environment!
|
||||
begin
|
||||
exec args.join(" ")
|
||||
rescue Errno::EACCES
|
||||
error "not executable: #{args.first}"
|
||||
rescue Errno::ENOENT
|
||||
error "command not found: #{args.first}"
|
||||
end
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
@@ -63,7 +88,7 @@ private ######################################################################
|
||||
end
|
||||
|
||||
def engine
|
||||
@engine ||= Foreman::Engine.new(procfile)
|
||||
@engine ||= Foreman::Engine.new(procfile, options)
|
||||
end
|
||||
|
||||
def procfile
|
||||
@@ -86,8 +111,7 @@ private ######################################################################
|
||||
def options
|
||||
original_options = super
|
||||
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))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -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
|
||||
+147
-114
@@ -1,193 +1,226 @@
|
||||
require "foreman"
|
||||
require "foreman/process"
|
||||
require "foreman/procfile"
|
||||
require "foreman/utils"
|
||||
require "pty"
|
||||
require "tempfile"
|
||||
require "timeout"
|
||||
require "term/ansicolor"
|
||||
require "fileutils"
|
||||
require "thread"
|
||||
|
||||
class Foreman::Engine
|
||||
|
||||
attr_reader :procfile
|
||||
attr_reader :directory
|
||||
attr_reader :options
|
||||
|
||||
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)
|
||||
@procfile = read_procfile(procfile)
|
||||
@directory = File.expand_path(File.dirname(procfile))
|
||||
def initialize(procfile, options={})
|
||||
@procfile = Foreman::Procfile.new(procfile)
|
||||
@directory = options[:app_root] || File.expand_path(File.dirname(procfile))
|
||||
@options = options
|
||||
@environment = read_environment_files(options[:env])
|
||||
@output_mutex = Mutex.new
|
||||
end
|
||||
|
||||
def processes
|
||||
@processes ||= begin
|
||||
@order = []
|
||||
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
|
||||
def self.load_env!(env_file)
|
||||
@environment = read_environment_files(env_file)
|
||||
apply_environment!
|
||||
end
|
||||
|
||||
def process_order
|
||||
processes
|
||||
@order.uniq
|
||||
end
|
||||
|
||||
def processes_in_order
|
||||
process_order.map do |name|
|
||||
[name, processes[name]]
|
||||
end
|
||||
end
|
||||
|
||||
def start(options={})
|
||||
def start
|
||||
proctitle "ruby: foreman master"
|
||||
termtitle "#{File.basename(@directory)} - foreman"
|
||||
|
||||
processes_in_order.each do |name, process|
|
||||
fork process, options
|
||||
end
|
||||
|
||||
trap("TERM") { puts "SIGTERM received"; kill_all("TERM") }
|
||||
trap("INT") { puts "SIGINT received"; kill_all("INT") }
|
||||
|
||||
watch_for_termination
|
||||
end
|
||||
|
||||
def execute(name, options={})
|
||||
fork processes[name], options
|
||||
|
||||
trap("TERM") { puts "SIGTERM received"; kill_all("TERM") }
|
||||
trap("INT") { puts "SIGINT received"; kill_all("INT") }
|
||||
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
||||
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
||||
|
||||
assign_colors
|
||||
spawn_processes
|
||||
watch_for_output
|
||||
watch_for_termination
|
||||
end
|
||||
|
||||
def port_for(process, num, base_port=nil)
|
||||
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
|
||||
end
|
||||
|
||||
private ######################################################################
|
||||
|
||||
def fork(process, options={})
|
||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
def spawn_processes
|
||||
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
|
||||
|
||||
1.upto(concurrency[process.name]) do |num|
|
||||
fork_individual(process, num, port_for(process, num, options[:port]))
|
||||
procfile.entries.each do |entry|
|
||||
reader, writer = IO.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
|
||||
|
||||
def fork_individual(process, num, port)
|
||||
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
|
||||
def base_port
|
||||
options[:port] || 5000
|
||||
end
|
||||
|
||||
def run(process)
|
||||
proctitle "ruby: foreman #{process.name}"
|
||||
def kill_all(signal="SIGTERM")
|
||||
running_processes.each do |pid, process|
|
||||
info "sending #{signal} to pid #{pid}"
|
||||
process.kill signal
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
Dir.chdir directory do
|
||||
command = process.command
|
||||
def terminate_gracefully
|
||||
info "sending SIGTERM to all processes"
|
||||
kill_all "SIGTERM"
|
||||
Timeout.timeout(5) { Process.waitall }
|
||||
rescue Timeout::Error
|
||||
info "sending SIGKILL to all processes"
|
||||
kill_all "SIGKILL"
|
||||
end
|
||||
|
||||
PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
|
||||
until stdin.eof?
|
||||
info stdin.gets, process
|
||||
def watch_for_output
|
||||
Thread.new do
|
||||
require "win32console" if Foreman.windows?
|
||||
begin
|
||||
loop do
|
||||
rs, ws = IO.select(readers.values, [], [], 1)
|
||||
(rs || []).each do |r|
|
||||
data = r.gets
|
||||
next unless data
|
||||
ps, message = data.split(",", 2)
|
||||
color = colors[ps.split(".").first]
|
||||
info message, ps, color
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue PTY::ChildExited, Interrupt
|
||||
begin
|
||||
info "process exiting", process
|
||||
rescue Interrupt
|
||||
rescue Exception => ex
|
||||
puts ex.message
|
||||
puts ex.backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def kill_all(signal="TERM")
|
||||
info "terminating"
|
||||
running_processes.each do |pid, process|
|
||||
info "killing #{process.name} in pid #{pid}"
|
||||
Process.kill(signal, pid)
|
||||
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, process=nil)
|
||||
print process.color if process
|
||||
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
|
||||
def info(message, name="system", color=Term::ANSIColor.white)
|
||||
print color
|
||||
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
||||
print Term::ANSIColor.reset
|
||||
print message.chomp
|
||||
puts
|
||||
puts ""
|
||||
end
|
||||
|
||||
def print(message=nil)
|
||||
@output_mutex.synchronize do
|
||||
$stdout.print message
|
||||
end
|
||||
end
|
||||
|
||||
def puts(message=nil)
|
||||
@output_mutex.synchronize do
|
||||
$stdout.puts message
|
||||
end
|
||||
end
|
||||
|
||||
def longest_process_name
|
||||
@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
|
||||
end
|
||||
end
|
||||
|
||||
def pad_process_name(process)
|
||||
name = process ? "#{ENV["PS"]}" : "system"
|
||||
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
|
||||
def pad_process_name(name="system")
|
||||
name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
|
||||
end
|
||||
|
||||
def proctitle(title)
|
||||
$0 = title
|
||||
end
|
||||
|
||||
def read_procfile(procfile)
|
||||
File.read(procfile)
|
||||
end
|
||||
|
||||
def watch_for_termination
|
||||
pid, status = Process.wait2
|
||||
process = running_processes.delete(pid)
|
||||
info "process terminated", process
|
||||
kill_all
|
||||
Process.waitall
|
||||
def termtitle(title)
|
||||
printf("\033]0;#{title}\007") unless Foreman.windows?
|
||||
end
|
||||
|
||||
def running_processes
|
||||
@running_processes ||= {}
|
||||
end
|
||||
|
||||
def readers
|
||||
@readers ||= {}
|
||||
end
|
||||
|
||||
def colors
|
||||
@colors ||= {}
|
||||
end
|
||||
|
||||
def assign_colors
|
||||
procfile.entries.each do |entry|
|
||||
colors[entry.name] = next_color
|
||||
end
|
||||
end
|
||||
|
||||
def process_by_reader(reader)
|
||||
readers.invert[reader]
|
||||
end
|
||||
|
||||
def next_color
|
||||
@current_color ||= -1
|
||||
@current_color += 1
|
||||
@current_color >= COLORS.length ? "" : COLORS[@current_color]
|
||||
@current_color = 0 if COLORS.length < @current_color
|
||||
COLORS[@current_color]
|
||||
end
|
||||
|
||||
def warn_deprecated_procfile!
|
||||
return if @already_warned_deprecated
|
||||
@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"
|
||||
module Env
|
||||
attr_reader :environment
|
||||
|
||||
def read_environment_files(filenames)
|
||||
environment = {}
|
||||
|
||||
(filenames || "").split(",").map(&:strip).each do |filename|
|
||||
error "No such file: #{filename}" unless File.exists?(filename)
|
||||
environment.merge!(read_environment(filename))
|
||||
end
|
||||
|
||||
environment.merge!(read_environment(".env")) unless filenames
|
||||
environment
|
||||
end
|
||||
|
||||
def read_environment(filename)
|
||||
return {} unless File.exists?(filename)
|
||||
|
||||
File.read(filename).split("\n").inject({}) do |hash, line|
|
||||
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
|
||||
hash[$1] = $2
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
def apply_environment!
|
||||
@environment.each { |k,v| ENV[k] = v }
|
||||
end
|
||||
|
||||
def error(message)
|
||||
puts "ERROR: #{message}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
include Env
|
||||
extend Env
|
||||
end
|
||||
|
||||
@@ -4,5 +4,8 @@ module Foreman::Export
|
||||
class Exception < ::Exception; end
|
||||
end
|
||||
|
||||
require "foreman/export/base"
|
||||
require "foreman/export/inittab"
|
||||
require "foreman/export/upstart"
|
||||
require "foreman/export/bluepill"
|
||||
require "foreman/export/runit"
|
||||
|
||||
@@ -3,10 +3,17 @@ require "foreman/utils"
|
||||
|
||||
class Foreman::Export::Base
|
||||
|
||||
attr_reader :engine
|
||||
attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
|
||||
|
||||
def initialize(engine)
|
||||
@engine = engine
|
||||
def initialize(location, engine, options={})
|
||||
@location = location
|
||||
@engine = engine
|
||||
@app = options[:app]
|
||||
@log = options[:log]
|
||||
@port = options[:port]
|
||||
@user = options[:user]
|
||||
@template = options[:template]
|
||||
@concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
end
|
||||
|
||||
def export
|
||||
@@ -23,8 +30,14 @@ private ######################################################################
|
||||
puts "[foreman export] %s" % message
|
||||
end
|
||||
|
||||
def export_template(name)
|
||||
File.read(File.expand_path("../../../../data/export/#{name}", __FILE__))
|
||||
def export_template(exporter, file, template_root)
|
||||
if template_root && File.exist?(file_path = File.join(template_root, file))
|
||||
File.read(file_path)
|
||||
elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
|
||||
File.read(file_path)
|
||||
else
|
||||
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
|
||||
end
|
||||
end
|
||||
|
||||
def write_file(filename, contents)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
require "erb"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Bluepill < Foreman::Export::Base
|
||||
|
||||
def export
|
||||
error("Must specify a location") unless location
|
||||
|
||||
FileUtils.mkdir_p location
|
||||
|
||||
app = self.app || File.basename(engine.directory)
|
||||
user = self.user || app
|
||||
log_root = self.log || "/var/log/#{app}"
|
||||
template_root = self.template
|
||||
|
||||
Dir["#{location}/#{app}.pill"].each do |file|
|
||||
say "cleaning up: #{file}"
|
||||
FileUtils.rm(file)
|
||||
end
|
||||
|
||||
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,21 +1,19 @@
|
||||
require "foreman/export/base"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Inittab < Foreman::Export::Base
|
||||
|
||||
def export(fname=nil, options={})
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
user = options[:user] || app
|
||||
log_root = options[:log] || "/var/log/#{app}"
|
||||
|
||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
def export
|
||||
app = self.app || File.basename(engine.directory)
|
||||
user = self.user || app
|
||||
log_root = self.log || "/var/log/#{app}"
|
||||
|
||||
inittab = []
|
||||
inittab << "# ----- foreman #{app} processes -----"
|
||||
|
||||
engine.processes.values.inject(1) do |index, process|
|
||||
1.upto(concurrency[process.name]) do |num|
|
||||
engine.procfile.entries.inject(1) do |index, process|
|
||||
1.upto(self.concurrency[process.name]) do |num|
|
||||
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
||||
port = engine.port_for(process, num, options[:port])
|
||||
port = engine.port_for(process, num, self.port)
|
||||
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
|
||||
index += 1
|
||||
end
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
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
|
||||
error("Must specify a location") unless location
|
||||
|
||||
app = self.app || File.basename(engine.directory)
|
||||
user = self.user || app
|
||||
log_root = self.log || "/var/log/#{app}"
|
||||
template_root = self.template
|
||||
|
||||
run_template = export_template('runit', 'run.erb', template_root)
|
||||
log_run_template = export_template('runit', 'log_run.erb', template_root)
|
||||
|
||||
engine.procfile.entries.each do |process|
|
||||
1.upto(self.concurrency[process.name]) do |num|
|
||||
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
|
||||
process_env_directory = "#{process_directory}/env"
|
||||
process_log_directory = "#{process_directory}/log"
|
||||
|
||||
create_directory process_directory
|
||||
create_directory process_env_directory
|
||||
create_directory process_log_directory
|
||||
|
||||
run = ERB.new(run_template).result(binding)
|
||||
write_file "#{process_directory}/run", run
|
||||
FileUtils.chmod 0755, "#{process_directory}/run"
|
||||
|
||||
port = engine.port_for(process, num, self.port)
|
||||
environment_variables = {'PORT' => port}.
|
||||
merge(engine.environment).
|
||||
merge(inline_variables(process.command))
|
||||
|
||||
environment_variables.each_pair do |var, env|
|
||||
write_file "#{process_env_directory}/#{var.upcase}", env
|
||||
end
|
||||
|
||||
log_run = ERB.new(log_run_template).result(binding)
|
||||
write_file "#{process_log_directory}/run", log_run
|
||||
FileUtils.chmod 0755, "#{process_log_directory}/run"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
def create_directory(location)
|
||||
say "creating: #{location}"
|
||||
FileUtils.mkdir_p(location)
|
||||
end
|
||||
|
||||
def inline_variables(command)
|
||||
variable_name_regex =
|
||||
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
|
||||
end
|
||||
end
|
||||
@@ -1,37 +1,37 @@
|
||||
require "erb"
|
||||
require "foreman/export/base"
|
||||
require "foreman/export"
|
||||
|
||||
class Foreman::Export::Upstart < Foreman::Export::Base
|
||||
|
||||
def export(location, options={})
|
||||
def export
|
||||
error("Must specify a location") unless location
|
||||
|
||||
FileUtils.mkdir_p location
|
||||
|
||||
app = options[:app] || File.basename(engine.directory)
|
||||
user = options[:user] || app
|
||||
log_root = options[:log] || "/var/log/#{app}"
|
||||
app = self.app || File.basename(engine.directory)
|
||||
user = self.user || app
|
||||
log_root = self.log || "/var/log/#{app}"
|
||||
template_root = self.template
|
||||
|
||||
Dir["#{location}/#{app}*.conf"].each do |file|
|
||||
say "cleaning up: #{file}"
|
||||
FileUtils.rm(file)
|
||||
end
|
||||
|
||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||
|
||||
master_template = export_template("upstart/master.conf.erb")
|
||||
master_template = export_template("upstart", "master.conf.erb", template_root)
|
||||
master_config = ERB.new(master_template).result(binding)
|
||||
write_file "#{location}/#{app}.conf", master_config
|
||||
|
||||
process_template = export_template("upstart/process.conf.erb")
|
||||
process_template = export_template("upstart", "process.conf.erb", template_root)
|
||||
|
||||
engine.processes.values.each do |process|
|
||||
process_master_template = export_template("upstart/process_master.conf.erb")
|
||||
engine.procfile.entries.each do |process|
|
||||
next if (conc = self.concurrency[process.name]) < 1
|
||||
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
||||
process_master_config = ERB.new(process_master_template).result(binding)
|
||||
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
||||
|
||||
1.upto(concurrency[process.name]) do |num|
|
||||
port = engine.port_for(process, num, options[:port])
|
||||
1.upto(self.concurrency[process.name]) do |num|
|
||||
port = engine.port_for(process, num, self.port)
|
||||
process_config = ERB.new(process_template).result(binding)
|
||||
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
|
||||
end
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
module Foreman::Helpers
|
||||
# Copied whole sale from, https://github.com/defunkt/resque/
|
||||
|
||||
# Given a word with dashes, returns a camel cased version of it.
|
||||
#
|
||||
# classify('job-name') # => 'JobName'
|
||||
def classify(dashed_word)
|
||||
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
||||
end
|
||||
|
||||
# Tries to find a constant with the name specified in the argument string:
|
||||
#
|
||||
# constantize("Module") # => Module
|
||||
# constantize("Test::Unit") # => Test::Unit
|
||||
#
|
||||
# The name is assumed to be the one of a top-level constant, no matter
|
||||
# whether it starts with "::" or not. No lexical context is taken into
|
||||
# account:
|
||||
#
|
||||
# C = 'outside'
|
||||
# module M
|
||||
# C = 'inside'
|
||||
# C # => 'inside'
|
||||
# constantize("C") # => 'outside', same as ::C
|
||||
# end
|
||||
#
|
||||
# NameError is raised when the constant is unknown.
|
||||
def constantize(camel_cased_word)
|
||||
camel_cased_word = camel_cased_word.to_s
|
||||
|
||||
if camel_cased_word.include?('-')
|
||||
camel_cased_word = classify(camel_cased_word)
|
||||
end
|
||||
|
||||
names = camel_cased_word.split('::')
|
||||
names.shift if names.empty? || names.first.empty?
|
||||
|
||||
constant = Object
|
||||
names.each do |name|
|
||||
args = Module.method(:const_get).arity != 1 ? [false] : []
|
||||
|
||||
if constant.const_defined?(name, *args)
|
||||
constant = constant.const_get(name)
|
||||
else
|
||||
constant = constant.const_missing(name)
|
||||
end
|
||||
end
|
||||
constant
|
||||
end
|
||||
end
|
||||
+88
-6
@@ -1,14 +1,96 @@
|
||||
require "foreman"
|
||||
require "rubygems"
|
||||
|
||||
class Foreman::Process
|
||||
|
||||
attr_reader :name
|
||||
attr_reader :command
|
||||
attr_accessor :color
|
||||
attr_reader :entry
|
||||
attr_reader :num
|
||||
attr_reader :pid
|
||||
attr_reader :port
|
||||
|
||||
def initialize(name, command)
|
||||
@name = name
|
||||
@command = command
|
||||
def initialize(entry, num, port)
|
||||
@entry = entry
|
||||
@num = num
|
||||
@port = port
|
||||
end
|
||||
|
||||
def run(pipe, basedir, environment)
|
||||
with_environment(environment.merge("PORT" => port.to_s)) do
|
||||
run_process basedir, entry.command, pipe
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
"%s.%s" % [ entry.name, num ]
|
||||
end
|
||||
|
||||
def kill(signal)
|
||||
pid && Process.kill(signal, pid)
|
||||
rescue Errno::ESRCH
|
||||
false
|
||||
end
|
||||
|
||||
def detach
|
||||
pid && Process.detach(pid)
|
||||
end
|
||||
|
||||
def alive?
|
||||
kill(0)
|
||||
end
|
||||
|
||||
def dead?
|
||||
!alive?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fork_with_io(command, basedir)
|
||||
reader, writer = IO.pipe
|
||||
command = replace_command_env(command)
|
||||
pid = if Foreman.windows?
|
||||
Dir.chdir(basedir) do
|
||||
Process.spawn command, :out => writer, :err => writer
|
||||
end
|
||||
elsif Foreman.jruby?
|
||||
require "posix/spawn"
|
||||
POSIX::Spawn.spawn(Foreman.runner, "-d", basedir, command, {
|
||||
:out => writer, :err => writer
|
||||
})
|
||||
else
|
||||
fork do
|
||||
writer.sync = true
|
||||
$stdout.reopen writer
|
||||
$stderr.reopen writer
|
||||
reader.close
|
||||
exec Foreman.runner, "-d", basedir, command
|
||||
end
|
||||
end
|
||||
[ reader, pid ]
|
||||
end
|
||||
|
||||
def run_process(basedir, command, pipe)
|
||||
io, @pid = fork_with_io(command, basedir)
|
||||
output pipe, "started with pid %d" % @pid
|
||||
Thread.new do
|
||||
until io.eof?
|
||||
output pipe, io.gets
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def output(pipe, message)
|
||||
pipe.puts "%s,%s" % [ name, message ]
|
||||
end
|
||||
|
||||
def replace_command_env(command)
|
||||
command.gsub(/\$(\w+)/) { |e| ENV[e[1..-1]] }
|
||||
end
|
||||
|
||||
def with_environment(environment)
|
||||
original = ENV.to_hash
|
||||
ENV.update environment
|
||||
yield
|
||||
ensure
|
||||
ENV.replace original
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -3,9 +3,12 @@ require "foreman"
|
||||
class Foreman::Utils
|
||||
|
||||
def self.parse_concurrency(concurrency)
|
||||
@concurrency ||= begin
|
||||
begin
|
||||
pairs = concurrency.to_s.gsub(/\s/, "").split(",")
|
||||
pairs.inject(Hash.new(1)) do |hash, pair|
|
||||
|
||||
default = concurrency.nil? ? 1 : 0
|
||||
|
||||
pairs.inject(Hash.new(default)) do |hash, pair|
|
||||
process, amount = pair.split("=")
|
||||
hash.update(process => amount.to_i)
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Foreman
|
||||
|
||||
VERSION = "0.13.1"
|
||||
VERSION = "0.37.0.pre4"
|
||||
|
||||
end
|
||||
|
||||
+31
-3
@@ -1,7 +1,7 @@
|
||||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "FOREMAN" "1" "May 2011" "Foreman 0.13.0" "Foreman Manual"
|
||||
.TH "FOREMAN" "1" "January 2012" "Foreman 0.33.1" "Foreman Manual"
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBforeman\fR \- manage Procfile\-based applications
|
||||
@@ -68,6 +68,14 @@ Specify the user the application should be run as\. Defaults to the app name
|
||||
These options control all modes of foreman\'s operation\.
|
||||
.
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-directory\fR
|
||||
Specify an alternate application root\. This defaults to the directory containing the Procfile\.
|
||||
.
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-env\fR
|
||||
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
|
||||
.
|
||||
.TP
|
||||
\fB\-f\fR, \fB\-\-procfile\fR
|
||||
Specify an alternate location for the application\'s Procfile\. This file\'s containing directory will be assumed to be the root directory of the application\.
|
||||
.
|
||||
@@ -75,9 +83,15 @@ Specify an alternate location for the application\'s Procfile\. This file\'s con
|
||||
foreman currently supports the following output formats:
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
bluepill
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
inittab
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
runit
|
||||
.
|
||||
.IP "\(bu" 4
|
||||
upstart
|
||||
.
|
||||
.IP "" 0
|
||||
@@ -125,7 +139,7 @@ job: bundle exec rake jobs:work
|
||||
.IP "" 0
|
||||
.
|
||||
.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
|
||||
.
|
||||
@@ -137,6 +151,20 @@ $ foreman check
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "ENVIRONMENT"
|
||||
If a \fB\.env\fR file exists in the current directory, the default environment will be read from it\. This file should contain key/value pairs, separated by \fB=\fR, with one key/value pair per line\.
|
||||
.
|
||||
.IP "" 4
|
||||
.
|
||||
.nf
|
||||
|
||||
FOO=bar
|
||||
BAZ=qux
|
||||
.
|
||||
.fi
|
||||
.
|
||||
.IP "" 0
|
||||
.
|
||||
.SH "DEFAULT OPTIONS"
|
||||
If a \fB\.foreman\fR file exists in the current directory, default options will be read from it\. This file should be in YAML format with the long option name as keys\. Example:
|
||||
.
|
||||
@@ -144,7 +172,7 @@ If a \fB\.foreman\fR file exists in the current directory, default options will
|
||||
.
|
||||
.nf
|
||||
|
||||
concurrency: alpha=0
|
||||
concurrency: alpha=0,bravo=1
|
||||
port: 15000
|
||||
.
|
||||
.fi
|
||||
|
||||
+24
-2
@@ -66,6 +66,14 @@ The following options control how the application is run:
|
||||
|
||||
These options control all modes of foreman's operation.
|
||||
|
||||
* `-d`, `--directory`:
|
||||
Specify an alternate application root. This defaults to the directory
|
||||
containing the Procfile.
|
||||
|
||||
* `-e`, `--env`:
|
||||
Specify an alternate environment file. You can specify more than one
|
||||
file by using: `--env file1,file2`.
|
||||
|
||||
* `-f`, `--procfile`:
|
||||
Specify an alternate location for the application's Procfile. This file's
|
||||
containing directory will be assumed to be the root directory of the
|
||||
@@ -75,8 +83,12 @@ These options control all modes of foreman's operation.
|
||||
|
||||
foreman currently supports the following output formats:
|
||||
|
||||
* bluepill
|
||||
|
||||
* inittab
|
||||
|
||||
* runit
|
||||
|
||||
* upstart
|
||||
|
||||
## INITTAB EXPORT
|
||||
@@ -107,17 +119,27 @@ to run it.
|
||||
web: bundle exec thin start
|
||||
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
|
||||
|
||||
## ENVIRONMENT
|
||||
|
||||
If a `.env` file exists in the current directory, the default environment will
|
||||
be read from it. This file should contain key/value pairs, separated by `=`, with
|
||||
one key/value pair per line.
|
||||
|
||||
FOO=bar
|
||||
BAZ=qux
|
||||
|
||||
## DEFAULT OPTIONS
|
||||
|
||||
If a `.foreman` file exists in the current directory, default options will
|
||||
be read from it. This file should be in YAML format with the long option
|
||||
name as keys. Example:
|
||||
|
||||
concurrency: alpha=0
|
||||
concurrency: alpha=0,bravo=1
|
||||
port: 15000
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require "spec_helper"
|
||||
require "foreman/cli"
|
||||
|
||||
describe "Foreman::CLI" do
|
||||
describe "Foreman::CLI", :fakefs do
|
||||
subject { Foreman::CLI.new }
|
||||
|
||||
describe "start" do
|
||||
@@ -19,13 +19,24 @@ describe "Foreman::CLI" do
|
||||
|
||||
it "runs successfully" do
|
||||
dont_allow(subject).error
|
||||
mock.instance_of(Foreman::Engine).start({})
|
||||
mock.instance_of(Foreman::Engine).start
|
||||
subject.start
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "export" do
|
||||
describe "options" do
|
||||
it "respects --env" do
|
||||
write_procfile
|
||||
write_env("envfile")
|
||||
mock_export = mock(Foreman::Export::Upstart)
|
||||
mock(Foreman::Export::Upstart).new("/upstart", is_a(Foreman::Engine), { "env" => "envfile" }) { mock_export }
|
||||
mock_export.export
|
||||
foreman %{ export upstart /upstart --env envfile }
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a non-existent Procfile" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Procfile does not exist.") do
|
||||
@@ -40,7 +51,7 @@ describe "Foreman::CLI" do
|
||||
|
||||
describe "with an invalid formatter" do
|
||||
it "prints an error" do
|
||||
mock_error(subject, "Unknown export format: invalidformatter.") do
|
||||
mock_error(subject, "Unknown export format: invalidformatter (unable to load file 'foreman/export/invalidformatter').") do
|
||||
subject.export("invalidformatter")
|
||||
end
|
||||
end
|
||||
@@ -51,7 +62,9 @@ describe "Foreman::CLI" do
|
||||
|
||||
it "runs successfully" do
|
||||
dont_allow(subject).error
|
||||
mock.instance_of(Foreman::Export::Upstart).export("/tmp/foo", {})
|
||||
mock_export = mock(Foreman::Export::Upstart)
|
||||
mock(Foreman::Export::Upstart).new("/tmp/foo", is_a(Foreman::Engine), {}) { mock_export }
|
||||
mock_export.export
|
||||
subject.export("upstart", "/tmp/foo")
|
||||
end
|
||||
end
|
||||
@@ -81,4 +94,53 @@ describe "Foreman::CLI" do
|
||||
end
|
||||
end
|
||||
|
||||
describe "run" do
|
||||
describe "with a valid Procfile" do
|
||||
before { write_procfile }
|
||||
|
||||
describe "and a command" do
|
||||
let(:command) { ["ls", "-l"] }
|
||||
|
||||
before(:each) do
|
||||
stub(subject).exec
|
||||
end
|
||||
|
||||
it "should load the environment file" do
|
||||
write_env
|
||||
preserving_env do
|
||||
subject.run *command
|
||||
ENV["FOO"].should == "bar"
|
||||
end
|
||||
|
||||
ENV["FOO"].should be_nil
|
||||
end
|
||||
|
||||
it "should runute the command as a string" do
|
||||
mock(subject).exec(command.join(" "))
|
||||
subject.run *command
|
||||
end
|
||||
end
|
||||
|
||||
describe "and a non-existent command" do
|
||||
let(:command) { "iuhtngrglhulhdfg" }
|
||||
|
||||
it "should print an error" do
|
||||
mock_error(subject, "command not found: #{command}") do
|
||||
subject.run command
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "and a non-executable command" do
|
||||
let(:command) { __FILE__ }
|
||||
|
||||
it "should print an error" do
|
||||
mock_error(subject, "not executable: #{command}") do
|
||||
subject.run command
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
+51
-29
@@ -1,8 +1,8 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
|
||||
describe "Foreman::Engine" do
|
||||
subject { Foreman::Engine.new("Procfile") }
|
||||
describe "Foreman::Engine", :fakefs do
|
||||
subject { Foreman::Engine.new("Procfile", {}) }
|
||||
|
||||
describe "initialize" do
|
||||
describe "without an existing Procfile" do
|
||||
@@ -15,21 +15,8 @@ describe "Foreman::Engine" do
|
||||
before { write_procfile }
|
||||
|
||||
it "reads the processes" do
|
||||
subject.processes["alpha"].command.should == "./alpha"
|
||||
subject.processes["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
|
||||
subject.procfile["alpha"].command.should == "./alpha"
|
||||
subject.procfile["bravo"].command.should == "./bravo"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -37,28 +24,63 @@ describe "Foreman::Engine" do
|
||||
describe "start" do
|
||||
it "forks the processes" do
|
||||
write_procfile
|
||||
mock(subject).fork(subject.processes["alpha"], {})
|
||||
mock(subject).fork(subject.processes["bravo"], {})
|
||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./alpha", is_a(IO))
|
||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO))
|
||||
mock(subject).watch_for_output
|
||||
mock(subject).watch_for_termination
|
||||
subject.start
|
||||
end
|
||||
|
||||
it "handles concurrency" do
|
||||
write_procfile
|
||||
mock(subject).fork_individual(subject.processes["alpha"], 1, 5000)
|
||||
mock(subject).fork_individual(subject.processes["alpha"], 2, 5001)
|
||||
mock(subject).fork_individual(subject.processes["bravo"], 1, 5100)
|
||||
mock(subject).watch_for_termination
|
||||
subject.start(:concurrency => "alpha=2")
|
||||
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
|
||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./alpha", is_a(IO)).twice
|
||||
mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO)).never
|
||||
mock(engine).watch_for_output
|
||||
mock(engine).watch_for_termination
|
||||
engine.start
|
||||
end
|
||||
end
|
||||
|
||||
describe "execute" do
|
||||
it "runs the processes" do
|
||||
describe "environment" do
|
||||
before(:each) do
|
||||
write_procfile
|
||||
mock(subject).fork(subject.processes["alpha"], {})
|
||||
mock(subject).watch_for_termination
|
||||
subject.execute("alpha")
|
||||
stub(Process).fork
|
||||
end
|
||||
|
||||
it "should read if specified" do
|
||||
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
|
||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
||||
stub(engine).info
|
||||
mock(engine).spawn_processes
|
||||
mock(engine).watch_for_termination
|
||||
engine.environment.should == {"FOO"=>"baz"}
|
||||
engine.start
|
||||
end
|
||||
|
||||
it "should read more than one if specified" do
|
||||
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
||||
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
|
||||
stub(engine).info
|
||||
mock(engine).spawn_processes
|
||||
mock(engine).watch_for_termination
|
||||
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
|
||||
engine.start
|
||||
end
|
||||
|
||||
it "should fail if specified and doesnt exist" do
|
||||
mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
|
||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
||||
end
|
||||
|
||||
it "should read .env if none specified" do
|
||||
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
|
||||
engine = Foreman::Engine.new("Procfile")
|
||||
mock(engine).spawn_processes
|
||||
mock(engine).watch_for_termination
|
||||
engine.environment.should == {"FOO"=>"qoo"}
|
||||
engine.start
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/bluepill"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Bluepill, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:engine) { Foreman::Engine.new(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:bluepill) { Foreman::Export::Bluepill.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("bluepill") }
|
||||
before(:each) { stub(bluepill).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
bluepill.export
|
||||
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app.pill"))
|
||||
end
|
||||
|
||||
context "with concurrency" do
|
||||
let(:options) { Hash[:concurrency => "alpha=2"] }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
bluepill.export
|
||||
normalize_space(File.read("/tmp/init/app.pill")).should == normalize_space(example_export_file("bluepill/app-concurrency.pill"))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/runit"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Runit, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
|
||||
let(:engine) { Foreman::Engine.new(procfile) }
|
||||
let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, :concurrency => 'alpha=2,bravo=1') }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("runit") }
|
||||
before(:each) { stub(runit).say }
|
||||
before(:each) { stub(FakeFS::FileUtils).chmod }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
FileUtils.mkdir_p('/tmp/init')
|
||||
|
||||
runit.export
|
||||
|
||||
File.read("/tmp/init/app-alpha-1/run").should == example_export_file('runit/app-alpha-1-run')
|
||||
File.read("/tmp/init/app-alpha-1/log/run").should ==
|
||||
example_export_file('runit/app-alpha-1-log-run')
|
||||
File.read("/tmp/init/app-alpha-1/env/PORT").should == "5000\n"
|
||||
File.read("/tmp/init/app-alpha-1/env/BAR").should == "baz\n"
|
||||
|
||||
File.read("/tmp/init/app-alpha-2/run").should == example_export_file('runit/app-alpha-2-run')
|
||||
File.read("/tmp/init/app-alpha-2/log/run").should ==
|
||||
example_export_file('runit/app-alpha-2-log-run')
|
||||
File.read("/tmp/init/app-alpha-2/env/PORT").should == "5001\n"
|
||||
File.read("/tmp/init/app-alpha-2/env/BAR").should == "baz\n"
|
||||
|
||||
File.read("/tmp/init/app-bravo-1/run").should == example_export_file('runit/app-bravo-1-run')
|
||||
File.read("/tmp/init/app-bravo-1/log/run").should ==
|
||||
example_export_file('runit/app-bravo-1-log-run')
|
||||
File.read("/tmp/init/app-bravo-1/env/PORT").should == "5100\n"
|
||||
end
|
||||
|
||||
it "creates a full path to the export directory" do
|
||||
expect { runit.export }.to_not raise_error(Errno::ENOENT)
|
||||
end
|
||||
end
|
||||
@@ -1,2 +1,76 @@
|
||||
require "spec_helper"
|
||||
require "foreman/engine"
|
||||
require "foreman/export/upstart"
|
||||
require "tmpdir"
|
||||
|
||||
describe Foreman::Export::Upstart, :fakefs do
|
||||
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
||||
let(:engine) { Foreman::Engine.new(procfile) }
|
||||
let(:options) { Hash.new }
|
||||
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, options) }
|
||||
|
||||
before(:each) { load_export_templates_into_fakefs("upstart") }
|
||||
before(:each) { stub(upstart).say }
|
||||
|
||||
it "exports to the filesystem" do
|
||||
upstart.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-bravo.conf").should == example_export_file("upstart/app-bravo.conf")
|
||||
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("upstart/app-bravo-1.conf")
|
||||
end
|
||||
|
||||
context "with concurrency" do
|
||||
let(:options) { Hash[:concurrency => "alpha=2"] }
|
||||
|
||||
it "exports to the filesystem with concurrency" do
|
||||
upstart.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == example_export_file("upstart/app.conf")
|
||||
File.read("/tmp/init/app-alpha.conf").should == example_export_file("upstart/app-alpha.conf")
|
||||
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("upstart/app-alpha-1.conf")
|
||||
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("upstart/app-alpha-2.conf")
|
||||
File.exists?("/tmp/init/app-bravo-1.conf").should == false
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates" do
|
||||
let(:template_root) { "/tmp/alternate" }
|
||||
let(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, :template => template_root) }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p template_root
|
||||
File.open("#{template_root}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
upstart.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == "alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
context "with alternate templates from home dir" do
|
||||
let(:default_template_root) {File.expand_path("#{ENV['HOME']}/.foreman/templates")}
|
||||
|
||||
before do
|
||||
ENV['_FOREMAN_SPEC_HOME'] = ENV['HOME']
|
||||
ENV['HOME'] = "/home/appuser"
|
||||
FileUtils.mkdir_p default_template_root
|
||||
File.open("#{default_template_root}/master.conf.erb", "w") { |f| f.puts "default_alternate_template" }
|
||||
end
|
||||
|
||||
after do
|
||||
ENV['HOME'] = ENV.delete('_FOREMAN_SPEC_HOME')
|
||||
end
|
||||
|
||||
it "can export with alternate template files" do
|
||||
upstart.export
|
||||
|
||||
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,2 +1,131 @@
|
||||
require "spec_helper"
|
||||
require "foreman/process"
|
||||
require 'spec_helper'
|
||||
require 'foreman/process'
|
||||
require 'ostruct'
|
||||
require 'timeout'
|
||||
require 'tmpdir'
|
||||
|
||||
describe Foreman::Process do
|
||||
subject { described_class.new entry, number, port }
|
||||
|
||||
let(:number) { 1 }
|
||||
let(:port) { 777 }
|
||||
let(:command) { "script" }
|
||||
let(:name) { "foobar" }
|
||||
let(:entry) { OpenStruct.new :name => name, :command => command }
|
||||
|
||||
its(:entry) { entry }
|
||||
its(:num) { number }
|
||||
its(:port) { port }
|
||||
its(:name) { "#{name}.#{port}" }
|
||||
its(:pid) { nil }
|
||||
|
||||
describe '#run' do
|
||||
let(:pipe) { :pipe }
|
||||
let(:basedir) { Dir.mktmpdir }
|
||||
let(:env) {{ 'foo' => 'bar' }}
|
||||
let(:init_delta) { 0.1 }
|
||||
|
||||
after { FileUtils.remove_entry_secure basedir }
|
||||
|
||||
def run(cmd=command)
|
||||
entry.command = cmd
|
||||
subject.run pipe, basedir, env
|
||||
subject.detach && sleep(init_delta)
|
||||
end
|
||||
|
||||
def run_file(executable, code)
|
||||
file = File.open("#{basedir}/script", 'w') {|it| it << code }
|
||||
run "#{executable} #{file.path}"
|
||||
end
|
||||
|
||||
context 'options' do
|
||||
it 'should set PORT for environment' do
|
||||
mock(subject).run_process(basedir, command, pipe) do
|
||||
ENV['PORT'].should == port.to_s
|
||||
end
|
||||
run
|
||||
end
|
||||
|
||||
it 'should set custom variables for environment' do
|
||||
mock(subject).run_process(basedir, command, pipe) do
|
||||
ENV['foo'].should == 'bar'
|
||||
end
|
||||
run
|
||||
end
|
||||
|
||||
it 'should restore environment afterwards' do
|
||||
mock(subject).run_process(basedir, command, pipe)
|
||||
run
|
||||
ENV.should_not include('PORT', 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'process' do
|
||||
around do |spec|
|
||||
IO.pipe do |reader, writer|
|
||||
@reader, @writer = reader, writer
|
||||
spec.run
|
||||
end
|
||||
end
|
||||
|
||||
let(:pipe) { @writer }
|
||||
let(:output) { @reader.read_nonblock 1024 }
|
||||
|
||||
it 'should not block' do
|
||||
expect {
|
||||
Timeout.timeout(2*init_delta) { run 'sleep 2' }
|
||||
}.should_not raise_exception
|
||||
end
|
||||
|
||||
it 'should be alive' do
|
||||
run 'sleep 1'
|
||||
subject.should be_alive
|
||||
end
|
||||
|
||||
it 'should be dead' do
|
||||
run 'exit'
|
||||
subject.should be_dead
|
||||
end
|
||||
|
||||
it 'should be killable' do
|
||||
run 'sleep 1'
|
||||
subject.kill 'TERM'
|
||||
subject.should be_dead
|
||||
end
|
||||
|
||||
it 'should send different signals' do
|
||||
run_file 'ruby', <<-CODE
|
||||
trap "TERM", "IGNORE"
|
||||
loop { sleep 1 }
|
||||
CODE
|
||||
sleep 1 # wait for ruby to start
|
||||
subject.should be_alive
|
||||
subject.kill 'TERM'
|
||||
subject.should be_alive
|
||||
subject.kill 'KILL'
|
||||
subject.should be_dead
|
||||
end
|
||||
|
||||
it 'should redirect stdout' do
|
||||
run 'echo hey'
|
||||
output.should include('hey')
|
||||
end
|
||||
|
||||
it 'should redirect stderr' do
|
||||
run 'echo hey >2'
|
||||
output.should include('hey')
|
||||
end
|
||||
|
||||
it 'should handle variables' do
|
||||
run 'echo $PORT'
|
||||
output.should include('777')
|
||||
end
|
||||
|
||||
it 'should handle arguments' do
|
||||
pending
|
||||
run %{ sh -c "trap '' TERM; sleep 10" }
|
||||
subject.should be_alive
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,4 +8,21 @@ describe Foreman do
|
||||
it { should be_a String }
|
||||
end
|
||||
|
||||
describe "::load_env!(env_file)", :fakefs do
|
||||
after do
|
||||
ENV['FOO'] = nil
|
||||
end
|
||||
|
||||
it "should load env_file into ENV" do
|
||||
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
||||
Foreman.load_env!("/tmp/env1")
|
||||
ENV['FOO'].should == 'bar'
|
||||
end
|
||||
|
||||
it "should assume env_file in ./.env" do
|
||||
File.open("./.env", "w") { |f| f.puts("FOO=bar") }
|
||||
Foreman.load_env!
|
||||
ENV['FOO'].should == 'bar'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,44 @@
|
||||
Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
|
||||
|
||||
app.uid = "app"
|
||||
app.gid = "app"
|
||||
|
||||
|
||||
|
||||
|
||||
app.process("alpha-1") do |process|
|
||||
process.start_command = "./alpha"
|
||||
|
||||
process.working_dir = "/tmp/app"
|
||||
process.daemonize = true
|
||||
process.environment = {"PORT" => "5000"}
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
|
||||
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill -QUIT {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "app-alpha"
|
||||
end
|
||||
|
||||
app.process("bravo-1") do |process|
|
||||
process.start_command = "./bravo"
|
||||
|
||||
process.working_dir = "/tmp/app"
|
||||
process.daemonize = true
|
||||
process.environment = {"PORT" => "5100"}
|
||||
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
||||
|
||||
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
|
||||
|
||||
process.monitor_children do |children|
|
||||
children.stop_command "kill -QUIT {{PID}}"
|
||||
end
|
||||
|
||||
process.group = "app-bravo"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
start on starting app-alpha
|
||||
stop on stopping app-alpha
|
||||
respawn
|
||||
|
||||
exec su - app -c 'cd /tmp/app; export PORT=5000; ./alpha >> /var/log/app/alpha-1.log 2>&1'
|
||||
@@ -0,0 +1,5 @@
|
||||
start on starting app-alpha
|
||||
stop on stopping app-alpha
|
||||
respawn
|
||||
|
||||
exec su - app -c 'cd /tmp/app; export PORT=5001; ./alpha >> /var/log/app/alpha-2.log 2>&1'
|
||||
@@ -0,0 +1,2 @@
|
||||
start on starting app
|
||||
stop on stopping app
|
||||
@@ -0,0 +1,5 @@
|
||||
start on starting app-bravo
|
||||
stop on stopping app-bravo
|
||||
respawn
|
||||
|
||||
exec su - app -c 'cd /tmp/app; export PORT=5100; ./bravo >> /var/log/app/bravo-1.log 2>&1'
|
||||
@@ -0,0 +1,2 @@
|
||||
start on starting app
|
||||
stop on stopping app
|
||||
@@ -0,0 +1,8 @@
|
||||
pre-start script
|
||||
|
||||
bash << "EOF"
|
||||
mkdir -p /var/log/app
|
||||
chown -R app /var/log/app
|
||||
EOF
|
||||
|
||||
end script
|
||||
+54
-6
@@ -3,7 +3,7 @@ require "rspec"
|
||||
require "fakefs/safe"
|
||||
require "fakefs/spec_helpers"
|
||||
|
||||
$:.unshift "lib"
|
||||
$:.unshift File.expand_path("../../lib", __FILE__)
|
||||
|
||||
def mock_error(subject, message)
|
||||
mock_exit do
|
||||
@@ -12,6 +12,10 @@ def mock_error(subject, message)
|
||||
end
|
||||
end
|
||||
|
||||
def foreman(args)
|
||||
Foreman::CLI.start(args.split(" "))
|
||||
end
|
||||
|
||||
def mock_exit(&block)
|
||||
block.should raise_error(SystemExit)
|
||||
end
|
||||
@@ -24,15 +28,59 @@ def write_foreman_config(app)
|
||||
end
|
||||
end
|
||||
|
||||
def write_procfile(procfile="Procfile")
|
||||
def write_procfile(procfile="Procfile", alpha_env="")
|
||||
File.open(procfile, "w") do |file|
|
||||
file.puts "alpha: ./alpha"
|
||||
file.puts "bravo: ./bravo"
|
||||
file.puts "alpha: ./alpha" + " #{alpha_env}".rstrip
|
||||
file.puts "\n"
|
||||
file.puts "bravo:\t./bravo"
|
||||
end
|
||||
File.expand_path(procfile)
|
||||
end
|
||||
|
||||
def write_env(env=".env")
|
||||
File.open(env, "w") do |file|
|
||||
file.puts "FOO=bar"
|
||||
end
|
||||
end
|
||||
|
||||
Rspec.configure do |config|
|
||||
def load_export_templates_into_fakefs(type)
|
||||
FakeFS.deactivate!
|
||||
files = Dir[File.expand_path("../../data/export/#{type}/**", __FILE__)].inject({}) do |hash, file|
|
||||
hash.update(file => File.read(file))
|
||||
end
|
||||
FakeFS.activate!
|
||||
files.each do |filename, contents|
|
||||
File.open(filename, "w") do |f|
|
||||
f.puts contents
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def example_export_file(filename)
|
||||
FakeFS.deactivate!
|
||||
data = File.read(File.expand_path("../resources/export/#{filename}", __FILE__))
|
||||
FakeFS.activate!
|
||||
data
|
||||
end
|
||||
|
||||
def preserving_env
|
||||
old_env = ENV.to_hash
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
ENV.clear
|
||||
ENV.update(old_env)
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_space(s)
|
||||
s.gsub(/\n[\n\s]*/, "\n")
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.treat_symbols_as_metadata_keys_with_true_values = true
|
||||
config.color_enabled = true
|
||||
config.include FakeFS::SpecHelpers
|
||||
config.order = 'rand'
|
||||
config.include FakeFS::SpecHelpers, :fakefs
|
||||
config.mock_with :rr
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user