Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc95936327 | ||
|
|
0c27f78d46 | ||
|
|
356c61f471 | ||
|
|
dcff4da220 | ||
|
|
888520ee99 | ||
|
|
c7b6b334fd | ||
|
|
f476920a05 | ||
|
|
5436b68cf1 | ||
|
|
c9411cd2b1 | ||
|
|
6e95d1ce94 | ||
|
|
c5548a345e | ||
|
|
f668b87660 | ||
|
|
914a1ee958 | ||
|
|
e1c2946718 | ||
|
|
6160246da0 | ||
|
|
2e8030dbd4 | ||
|
|
58e4cdafd7 | ||
|
|
44a5dff724 | ||
|
|
e33921f083 | ||
|
|
79cf541ebe | ||
|
|
39ace26ae1 | ||
|
|
c383359136 | ||
|
|
a5e094353c | ||
|
|
12720b4c12 | ||
|
|
1c732a4658 |
+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:
|
||||||
|
- https://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]
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
foreman (0.26.0)
|
foreman (0.30.0)
|
||||||
term-ansicolor (~> 1.0.5)
|
term-ansicolor (~> 1.0.5)
|
||||||
thor (>= 0.13.6)
|
thor (>= 0.13.6)
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ GEM
|
|||||||
diff-lcs (~> 1.1.2)
|
diff-lcs (~> 1.1.2)
|
||||||
rspec-mocks (2.6.0)
|
rspec-mocks (2.6.0)
|
||||||
rubyzip (0.9.4)
|
rubyzip (0.9.4)
|
||||||
term-ansicolor (1.0.6)
|
term-ansicolor (1.0.7)
|
||||||
thor (0.14.6)
|
thor (0.14.6)
|
||||||
xml-simple (1.0.15)
|
xml-simple (1.0.15)
|
||||||
|
|
||||||
|
|||||||
@@ -166,3 +166,22 @@ end
|
|||||||
Dir[File.expand_path("../dist/**/*.rake", __FILE__)].each do |rake|
|
Dir[File.expand_path("../dist/**/*.rake", __FILE__)].each do |rake|
|
||||||
import rake
|
import rake
|
||||||
end
|
end
|
||||||
|
|
||||||
|
task :changelog do
|
||||||
|
timestamp = Time.now.utc.strftime('%m/%d/%Y')
|
||||||
|
sha = `git log | head -1`.split(' ').last
|
||||||
|
changelog = ["#{version} #{timestamp} #{sha}"]
|
||||||
|
changelog << ('=' * changelog[0].length)
|
||||||
|
changelog << ''
|
||||||
|
|
||||||
|
last_sha = `cat Changelog | head -1`.split(' ').last
|
||||||
|
shortlog = `git log #{last_sha}..HEAD --pretty=format:'%s [%an]'`
|
||||||
|
changelog << shortlog.split("\n")
|
||||||
|
changelog.concat ['', '', '']
|
||||||
|
|
||||||
|
old_changelog = File.read('Changelog')
|
||||||
|
File.open('Changelog', 'w') do |file|
|
||||||
|
file.write(changelog.join("\n"))
|
||||||
|
file.write(old_changelog)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec $1 2>&1
|
||||||
@@ -3,7 +3,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|||||||
app.uid = "<%= user %>"
|
app.uid = "<%= user %>"
|
||||||
app.gid = "<%= user %>"
|
app.gid = "<%= user %>"
|
||||||
|
|
||||||
<% engine.processes.each do |process| %>
|
<% engine.procfile.entries.each do |process| %>
|
||||||
<% 1.upto(concurrency[process.name]) do |num| %>
|
<% 1.upto(concurrency[process.name]) do |num| %>
|
||||||
<% port = engine.port_for(process, num, options[:port]) %>
|
<% port = engine.port_for(process, num, options[:port]) %>
|
||||||
app.process("<%= process.name %>-<%=num%>") do |process|
|
app.process("<%= process.name %>-<%=num%>") do |process|
|
||||||
@@ -19,7 +19,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill -QUIT {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "<%= app %>-<%= process.name %>"
|
process.group = "<%= app %>-<%= process.name %>"
|
||||||
end
|
end
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
+5
-10
@@ -8,20 +8,15 @@ 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)
|
|
||||||
else
|
|
||||||
engine.start
|
|
||||||
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"
|
||||||
@@ -55,8 +50,8 @@ class Foreman::CLI < Thor
|
|||||||
desc "check", "Validate your application's Procfile"
|
desc "check", "Validate your application's Procfile"
|
||||||
|
|
||||||
def check
|
def check
|
||||||
error "no processes defined" unless engine.processes.length > 0
|
error "no processes defined" unless engine.procfile.entries.length > 0
|
||||||
display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})"
|
display "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
||||||
end
|
end
|
||||||
|
|
||||||
private ######################################################################
|
private ######################################################################
|
||||||
|
|||||||
+102
-83
@@ -12,7 +12,6 @@ class Foreman::Engine
|
|||||||
|
|
||||||
attr_reader :procfile
|
attr_reader :procfile
|
||||||
attr_reader :directory
|
attr_reader :directory
|
||||||
attr_reader :environment
|
|
||||||
attr_reader :options
|
attr_reader :options
|
||||||
|
|
||||||
extend Term::ANSIColor
|
extend Term::ANSIColor
|
||||||
@@ -24,37 +23,27 @@ class Foreman::Engine
|
|||||||
@directory = File.expand_path(File.dirname(procfile))
|
@directory = File.expand_path(File.dirname(procfile))
|
||||||
@options = options
|
@options = options
|
||||||
@environment = read_environment_files(options[:env])
|
@environment = read_environment_files(options[:env])
|
||||||
|
@output_mutex = Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.load_env!(env_file)
|
||||||
|
@environment = read_environment_files(env_file)
|
||||||
|
apply_environment!
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
proctitle "ruby: foreman master"
|
proctitle "ruby: foreman master"
|
||||||
termtitle "#{File.basename(@directory)} - foreman (#{processes.size} processes)"
|
termtitle "#{File.basename(@directory)} - foreman"
|
||||||
|
|
||||||
processes.each do |process|
|
|
||||||
process.color = next_color
|
|
||||||
fork process
|
|
||||||
end
|
|
||||||
|
|
||||||
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
||||||
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
||||||
|
|
||||||
|
assign_colors
|
||||||
|
spawn_processes
|
||||||
|
watch_for_output
|
||||||
watch_for_termination
|
watch_for_termination
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(name)
|
|
||||||
error "no such process: #{name}" unless procfile[name]
|
|
||||||
fork procfile[name]
|
|
||||||
|
|
||||||
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
|
||||||
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
|
||||||
|
|
||||||
watch_for_termination
|
|
||||||
end
|
|
||||||
|
|
||||||
def processes
|
|
||||||
procfile.processes
|
|
||||||
end
|
|
||||||
|
|
||||||
def port_for(process, num, base_port=nil)
|
def port_for(process, num, base_port=nil)
|
||||||
base_port ||= 5000
|
base_port ||= 5000
|
||||||
offset = procfile.process_names.index(process.name) * 100
|
offset = procfile.process_names.index(process.name) * 100
|
||||||
@@ -63,47 +52,20 @@ class Foreman::Engine
|
|||||||
|
|
||||||
private ######################################################################
|
private ######################################################################
|
||||||
|
|
||||||
def fork(process)
|
def spawn_processes
|
||||||
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
|
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
|
||||||
|
|
||||||
1.upto(concurrency[process.name]) do |num|
|
procfile.entries.each do |entry|
|
||||||
fork_individual(process, num, port_for(process, num, @options[:port]))
|
reader, writer = IO.pipe
|
||||||
|
entry.spawn(concurrency[entry.name], writer, @directory, @environment, base_port).each do |process|
|
||||||
|
running_processes[process.pid] = process
|
||||||
|
readers[process] = reader
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fork_individual(process, num, port)
|
def base_port
|
||||||
@environment.each { |k,v| ENV[k] = v }
|
options[:port] || 5000
|
||||||
|
|
||||||
ENV["PORT"] = port.to_s
|
|
||||||
ENV["PS"] = "#{process.name}.#{num}"
|
|
||||||
|
|
||||||
pid = Process.fork do
|
|
||||||
run(process)
|
|
||||||
end
|
|
||||||
|
|
||||||
info "started with pid #{pid}", process
|
|
||||||
running_processes[pid] = process
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(process)
|
|
||||||
proctitle "ruby: foreman #{process.name}"
|
|
||||||
trap("SIGINT", "IGNORE")
|
|
||||||
|
|
||||||
begin
|
|
||||||
Dir.chdir directory do
|
|
||||||
PTY.spawn(process.command) do |stdin, stdout, pid|
|
|
||||||
trap("SIGTERM") { Process.kill("SIGTERM", pid) }
|
|
||||||
until stdin.eof?
|
|
||||||
info stdin.gets, process
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue PTY::ChildExited, Interrupt, Errno::EIO, Errno::ENOENT
|
|
||||||
begin
|
|
||||||
info "process exiting", process
|
|
||||||
rescue Interrupt
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def kill_all(signal="SIGTERM")
|
def kill_all(signal="SIGTERM")
|
||||||
@@ -115,27 +77,57 @@ private ######################################################################
|
|||||||
def terminate_gracefully
|
def terminate_gracefully
|
||||||
info "sending SIGTERM to all processes"
|
info "sending SIGTERM to all processes"
|
||||||
kill_all "SIGTERM"
|
kill_all "SIGTERM"
|
||||||
Timeout.timeout(3) { Process.waitall }
|
Timeout.timeout(5) { Process.waitall }
|
||||||
rescue Timeout::Error
|
rescue Timeout::Error
|
||||||
info "sending SIGKILL to all processes"
|
info "sending SIGKILL to all processes"
|
||||||
kill_all "SIGKILL"
|
kill_all "SIGKILL"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def watch_for_output
|
||||||
|
Thread.new do
|
||||||
|
begin
|
||||||
|
loop do
|
||||||
|
rs, ws = IO.select(readers.values, [], [], 1)
|
||||||
|
(rs || []).each do |r|
|
||||||
|
ps, message = r.gets.split(",", 2)
|
||||||
|
color = colors[ps.split(".").first]
|
||||||
|
info message, ps, color
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Exception => ex
|
||||||
|
puts ex.message
|
||||||
|
puts ex.backtrace
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def watch_for_termination
|
def watch_for_termination
|
||||||
pid, status = Process.wait2
|
pid, status = Process.wait2
|
||||||
process = running_processes.delete(pid)
|
process = running_processes.delete(pid)
|
||||||
info "process terminated", process
|
info "process terminated", process.name
|
||||||
terminate_gracefully
|
terminate_gracefully
|
||||||
kill_all
|
kill_all
|
||||||
rescue Errno::ECHILD
|
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
|
||||||
|
|
||||||
|
def print(message=nil)
|
||||||
|
@output_mutex.synchronize do
|
||||||
|
$stdout.print message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def puts(message=nil)
|
||||||
|
@output_mutex.synchronize do
|
||||||
|
$stdout.puts message
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def error(message)
|
def error(message)
|
||||||
@@ -151,9 +143,8 @@ private ######################################################################
|
|||||||
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
|
end
|
||||||
|
|
||||||
def proctitle(title)
|
def proctitle(title)
|
||||||
@@ -168,33 +159,61 @@ private ######################################################################
|
|||||||
@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 read_environment_files(filenames)
|
module Env
|
||||||
environment = {}
|
attr_reader :environment
|
||||||
|
|
||||||
(filenames || "").split(",").map(&:strip).each do |filename|
|
def read_environment_files(filenames)
|
||||||
error "No such file: #{filename}" unless File.exists?(filename)
|
environment = {}
|
||||||
environment.merge!(read_environment(filename))
|
|
||||||
end
|
|
||||||
|
|
||||||
environment.merge!(read_environment(".env")) unless filenames
|
(filenames || "").split(",").map(&:strip).each do |filename|
|
||||||
environment
|
error "No such file: #{filename}" unless File.exists?(filename)
|
||||||
end
|
environment.merge!(read_environment(filename))
|
||||||
|
|
||||||
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
|
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
include Env
|
||||||
|
extend Env
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Foreman::Export::Inittab < Foreman::Export::Base
|
|||||||
inittab = []
|
inittab = []
|
||||||
inittab << "# ----- foreman #{app} processes -----"
|
inittab << "# ----- foreman #{app} processes -----"
|
||||||
|
|
||||||
engine.processes.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])
|
||||||
|
|||||||
+16
-16
@@ -3,58 +3,58 @@ require "foreman/export"
|
|||||||
|
|
||||||
class Foreman::Export::Runit < Foreman::Export::Base
|
class Foreman::Export::Runit < Foreman::Export::Base
|
||||||
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
||||||
|
|
||||||
def export(location, options={})
|
def export(location, options={})
|
||||||
error("Must specify a location") unless location
|
error("Must specify a location") unless location
|
||||||
|
|
||||||
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]
|
template_root = options[:template]
|
||||||
|
|
||||||
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
||||||
|
|
||||||
run_template = export_template('runit', 'run.erb', template_root)
|
run_template = export_template('runit', 'run.erb', template_root)
|
||||||
log_run_template = export_template('runit', 'log_run.erb', template_root)
|
log_run_template = export_template('runit', 'log_run.erb', template_root)
|
||||||
|
|
||||||
engine.processes.each do |process|
|
engine.procfile.entries.each do |process|
|
||||||
1.upto(concurrency[process.name]) do |num|
|
1.upto(concurrency[process.name]) do |num|
|
||||||
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
|
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
|
||||||
process_env_directory = "#{process_directory}/env"
|
process_env_directory = "#{process_directory}/env"
|
||||||
process_log_directory = "#{process_directory}/log"
|
process_log_directory = "#{process_directory}/log"
|
||||||
|
|
||||||
create_directory process_directory
|
create_directory process_directory
|
||||||
create_directory process_env_directory
|
create_directory process_env_directory
|
||||||
create_directory process_log_directory
|
create_directory process_log_directory
|
||||||
|
|
||||||
run = ERB.new(run_template).result(binding)
|
run = ERB.new(run_template).result(binding)
|
||||||
write_file "#{process_directory}/run", run
|
write_file "#{process_directory}/run", run
|
||||||
|
|
||||||
port = engine.port_for(process, num, options[:port])
|
port = engine.port_for(process, num, options[:port])
|
||||||
environment_variables = {'PORT' => port}.
|
environment_variables = {'PORT' => port}.
|
||||||
merge(engine.environment).
|
merge(engine.environment).
|
||||||
merge(inline_variables(process.command))
|
merge(inline_variables(process.command))
|
||||||
|
|
||||||
environment_variables.each_pair do |var, env|
|
environment_variables.each_pair do |var, env|
|
||||||
write_file "#{process_env_directory}/#{var.upcase}", env
|
write_file "#{process_env_directory}/#{var.upcase}", env
|
||||||
end
|
end
|
||||||
|
|
||||||
log_run = ERB.new(log_run_template).result(binding)
|
log_run = ERB.new(log_run_template).result(binding)
|
||||||
write_file "#{process_log_directory}/run", log_run
|
write_file "#{process_log_directory}/run", log_run
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def create_directory(location)
|
def create_directory(location)
|
||||||
say "creating: #{location}"
|
say "creating: #{location}"
|
||||||
FileUtils.mkdir(location)
|
FileUtils.mkdir(location)
|
||||||
end
|
end
|
||||||
|
|
||||||
def inline_variables(command)
|
def inline_variables(command)
|
||||||
variable_name_regex =
|
variable_name_regex =
|
||||||
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
|
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
|
|||||||
|
|
||||||
process_template = export_template("upstart", "process.conf.erb", template_root)
|
process_template = export_template("upstart", "process.conf.erb", template_root)
|
||||||
|
|
||||||
engine.processes.each do |process|
|
engine.procfile.entries.each do |process|
|
||||||
next if (conc = concurrency[process.name]) < 1
|
next if (conc = concurrency[process.name]) < 1
|
||||||
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
||||||
process_master_config = ERB.new(process_master_template).result(binding)
|
process_master_config = ERB.new(process_master_template).result(binding)
|
||||||
|
|||||||
+63
-6
@@ -2,13 +2,70 @@ require "foreman"
|
|||||||
|
|
||||||
class Foreman::Process
|
class Foreman::Process
|
||||||
|
|
||||||
attr_reader :name
|
attr_reader :entry
|
||||||
attr_reader :command
|
attr_reader :num
|
||||||
attr_accessor :color
|
attr_reader :pid
|
||||||
|
attr_reader :port
|
||||||
|
|
||||||
def initialize(name, command)
|
def initialize(entry, num, port)
|
||||||
@name = name
|
@entry = entry
|
||||||
@command = command
|
@num = num
|
||||||
|
@port = port
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(pipe, basedir, environment)
|
||||||
|
Dir.chdir(basedir) do
|
||||||
|
with_environment(environment.merge("PORT" => port.to_s)) do
|
||||||
|
run_process entry.command, pipe
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def name
|
||||||
|
"%s.%s" % [ entry.name, num ]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fork_with_io(command)
|
||||||
|
io = case RUBY_VERSION
|
||||||
|
when /^1\.9\./
|
||||||
|
IO.popen([Foreman.runner, replace_command_env(command)], "w+")
|
||||||
|
when /^1\.8\./
|
||||||
|
full_command = replace_command_env(command).gsub("'", "\\'")
|
||||||
|
IO.popen("#{Foreman.runner} '#{full_command}'", "w+")
|
||||||
|
else
|
||||||
|
raise "Unknown Ruby version: #{RUBY_VERSION}"
|
||||||
|
end
|
||||||
|
[ io, io.pid ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_process(command, pipe)
|
||||||
|
io, @pid = fork_with_io(command)
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
require "foreman"
|
require "foreman"
|
||||||
|
require "foreman/procfile_entry"
|
||||||
|
|
||||||
# A valid Procfile entry is captured by this regex.
|
# A valid Procfile entry is captured by this regex.
|
||||||
# All other lines are ignored.
|
# All other lines are ignored.
|
||||||
@@ -10,18 +11,18 @@ require "foreman"
|
|||||||
#
|
#
|
||||||
class Foreman::Procfile
|
class Foreman::Procfile
|
||||||
|
|
||||||
attr_reader :processes
|
attr_reader :entries
|
||||||
|
|
||||||
def initialize(filename)
|
def initialize(filename)
|
||||||
@processes = parse_procfile(filename)
|
@entries = parse_procfile(filename)
|
||||||
end
|
|
||||||
|
|
||||||
def process_names
|
|
||||||
processes.map(&:name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](name)
|
def [](name)
|
||||||
processes.detect { |process| process.name == name }
|
entries.detect { |entry| entry.name == name }
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_names
|
||||||
|
entries.map(&:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -29,7 +30,7 @@ private
|
|||||||
def parse_procfile(filename)
|
def parse_procfile(filename)
|
||||||
File.read(filename).split("\n").map do |line|
|
File.read(filename).split("\n").map do |line|
|
||||||
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
|
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
|
||||||
Foreman::Process.new($1, $2)
|
Foreman::ProcfileEntry.new($1, $2)
|
||||||
end
|
end
|
||||||
end.compact
|
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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
module Foreman
|
module Foreman
|
||||||
|
|
||||||
VERSION = "0.26.0"
|
VERSION = "0.30.0"
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
+4
-1
@@ -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" "November 2011" "Foreman 0.25.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
|
||||||
@@ -85,6 +85,9 @@ bluepill
|
|||||||
inittab
|
inittab
|
||||||
.
|
.
|
||||||
.IP "\(bu" 4
|
.IP "\(bu" 4
|
||||||
|
runit
|
||||||
|
.
|
||||||
|
.IP "\(bu" 4
|
||||||
upstart
|
upstart
|
||||||
.
|
.
|
||||||
.IP "" 0
|
.IP "" 0
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ foreman currently supports the following output formats:
|
|||||||
|
|
||||||
* inittab
|
* inittab
|
||||||
|
|
||||||
|
* runit
|
||||||
|
|
||||||
* upstart
|
* upstart
|
||||||
|
|
||||||
## INITTAB EXPORT
|
## INITTAB EXPORT
|
||||||
|
|||||||
+12
-25
@@ -24,8 +24,9 @@ describe "Foreman::Engine" do
|
|||||||
describe "start" do
|
describe "start" do
|
||||||
it "forks the processes" do
|
it "forks the processes" do
|
||||||
write_procfile
|
write_procfile
|
||||||
mock(subject).fork(subject.procfile["alpha"])
|
mock.instance_of(Foreman::Process).run_process("./alpha", is_a(IO))
|
||||||
mock(subject).fork(subject.procfile["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
|
||||||
@@ -33,29 +34,14 @@ describe "Foreman::Engine" do
|
|||||||
it "handles concurrency" do
|
it "handles concurrency" do
|
||||||
write_procfile
|
write_procfile
|
||||||
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
|
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
|
||||||
mock(engine).fork_individual(engine.procfile["alpha"], 1, 5000)
|
mock.instance_of(Foreman::Process).run_process("./alpha", is_a(IO)).twice
|
||||||
mock(engine).fork_individual(engine.procfile["alpha"], 2, 5001)
|
mock.instance_of(Foreman::Process).run_process("./bravo", is_a(IO))
|
||||||
mock(engine).fork_individual(engine.procfile["bravo"], 1, 5100)
|
mock(engine).watch_for_output
|
||||||
mock(engine).watch_for_termination
|
mock(engine).watch_for_termination
|
||||||
engine.start
|
engine.start
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "execute" do
|
|
||||||
it "runs the processes" do
|
|
||||||
write_procfile
|
|
||||||
mock(subject).fork(subject.procfile["alpha"])
|
|
||||||
mock(subject).watch_for_termination
|
|
||||||
subject.execute("alpha")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "shows an error running a process that doesnt exist" do
|
|
||||||
write_procfile
|
|
||||||
mock(subject).puts("ERROR: no such process: foo")
|
|
||||||
lambda { subject.execute("foo") }.should raise_error(SystemExit)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "environment" do
|
describe "environment" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
write_procfile
|
write_procfile
|
||||||
@@ -66,9 +52,10 @@ describe "Foreman::Engine" do
|
|||||||
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
|
File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") }
|
||||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
||||||
stub(engine).info
|
stub(engine).info
|
||||||
|
mock(engine).spawn_processes
|
||||||
mock(engine).watch_for_termination
|
mock(engine).watch_for_termination
|
||||||
engine.environment.should == {"FOO"=>"baz"}
|
engine.environment.should == {"FOO"=>"baz"}
|
||||||
engine.execute("alpha")
|
engine.start
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should read more than one if specified" do
|
it "should read more than one if specified" do
|
||||||
@@ -76,9 +63,10 @@ describe "Foreman::Engine" do
|
|||||||
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
||||||
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
|
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
|
||||||
stub(engine).info
|
stub(engine).info
|
||||||
|
mock(engine).spawn_processes
|
||||||
mock(engine).watch_for_termination
|
mock(engine).watch_for_termination
|
||||||
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
|
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
|
||||||
engine.execute("alpha")
|
engine.start
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should fail if specified and doesnt exist" do
|
it "should fail if specified and doesnt exist" do
|
||||||
@@ -89,11 +77,10 @@ describe "Foreman::Engine" do
|
|||||||
it "should read .env if none specified" do
|
it "should read .env if none specified" do
|
||||||
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
|
File.open(".env", "w") { |f| f.puts("FOO=qoo") }
|
||||||
engine = Foreman::Engine.new("Procfile")
|
engine = Foreman::Engine.new("Procfile")
|
||||||
stub(engine).info
|
mock(engine).spawn_processes
|
||||||
mock(engine).watch_for_termination
|
mock(engine).watch_for_termination
|
||||||
mock(engine).fork_individual(anything, anything, anything)
|
|
||||||
engine.environment.should == {"FOO"=>"qoo"}
|
engine.environment.should == {"FOO"=>"qoo"}
|
||||||
engine.execute("bravo")
|
engine.start
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ describe Foreman::Export::Bluepill do
|
|||||||
|
|
||||||
it "exports to the filesystem" do
|
it "exports to the filesystem" do
|
||||||
bluepill.export("/tmp/init", :concurrency => "alpha=2")
|
bluepill.export("/tmp/init", :concurrency => "alpha=2")
|
||||||
|
|
||||||
File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill")
|
File.read("/tmp/init/app.pill").should == example_export_file("bluepill/app.pill")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill -QUIT {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "app-alpha"
|
process.group = "app-alpha"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill -QUIT {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "app-alpha"
|
process.group = "app-alpha"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|||||||
process.monitor_children do |children|
|
process.monitor_children do |children|
|
||||||
children.stop_command "kill -QUIT {{PID}}"
|
children.stop_command "kill -QUIT {{PID}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
process.group = "app-bravo"
|
process.group = "app-bravo"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user