commit 2dbd3e6be5f5a45ee3b236735130beaedc436bb2 Author: David Dollar Date: Mon May 17 01:36:30 2010 -0400 initial commit diff --git a/bin/foreman b/bin/foreman new file mode 100755 index 0000000..18f41af --- /dev/null +++ b/bin/foreman @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path("../../lib", __FILE__) + +require "foreman/cli" + +Foreman::CLI.start diff --git a/example/Procfile b/example/Procfile new file mode 100644 index 0000000..a5a42ba --- /dev/null +++ b/example/Procfile @@ -0,0 +1,3 @@ +neverdie ./never_die +diealot ./die_alot +error ./error \ No newline at end of file diff --git a/example/die_alot b/example/die_alot new file mode 100755 index 0000000..2fdb4d3 --- /dev/null +++ b/example/die_alot @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +sleep 2 +exit 0 diff --git a/example/error b/example/error new file mode 100755 index 0000000..30a7c2d --- /dev/null +++ b/example/error @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +sleep 10 +raise "Dying" diff --git a/example/never_die b/example/never_die new file mode 100755 index 0000000..25d3848 --- /dev/null +++ b/example/never_die @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +while true + sleep 5 +end diff --git a/lib/foreman.rb b/lib/foreman.rb new file mode 100644 index 0000000..63140f9 --- /dev/null +++ b/lib/foreman.rb @@ -0,0 +1,6 @@ +module Foreman + + VERSION = "0.0.1" + +end + diff --git a/lib/foreman/cli.rb b/lib/foreman/cli.rb new file mode 100644 index 0000000..8f8b72c --- /dev/null +++ b/lib/foreman/cli.rb @@ -0,0 +1,35 @@ +require "foreman" +require "foreman/engine" +require "foreman/export" +require "thor" + +class Foreman::CLI < Thor + + desc "start [PROCFILE]", "Run the app described in PROCFILE" + + def start(procfile="Procfile") + error "#{procfile} does not exist." unless File.exist?(procfile) + Foreman::Engine.new(procfile).start + end + + desc "export NAME [PROCFILE] [FORMAT]", "Export the app described in PROCFILE as NAME to another FORMAT" + + def export(name, procfile="Procfile", format="upstart") + error "#{procfile} does not exist." unless File.exist?(procfile) + + formatter = case format + when "upstart" then Foreman::Export::Upstart + else error "Unknown export format: #{format}" + end + + formatter.new(Foreman::Engine.new(procfile)).export(name) + end + +private ###################################################################### + + def error(message) + puts "ERROR: #{message}" + exit 1 + end + +end diff --git a/lib/foreman/engine.rb b/lib/foreman/engine.rb new file mode 100644 index 0000000..6abe6f6 --- /dev/null +++ b/lib/foreman/engine.rb @@ -0,0 +1,87 @@ +require "foreman" +require "foreman/process" + +class Foreman::Engine + + attr_reader :procfile + attr_reader :directory + + def initialize(procfile) + @procfile = File.read(procfile) + @directory = File.expand_path(File.dirname(procfile)) + end + + def processes + @processes ||= begin + procfile.split("\n").inject({}) do |hash, line| + next if line.strip == "" + process = Foreman::Process.new(*line.split(" ", 2)) + hash.update(process.name => process) + end + end + end + + def start + proctitle "ruby: foreman master" + + processes.each do |name, process| + fork process + end + + trap("TERM") { kill_and_exit("TERM") } + trap("INT") { kill_and_exit("INT") } + trap("INFO") { print_info } + + while true + pid, status = Process.wait2 + process = running_processes.delete(pid) + info "exited with code #{status}", process + fork process + end + end + +private ###################################################################### + + def fork(process) + pid = Process.fork do + proctitle "ruby: foreman #{process.name}" + + Dir.chdir directory do + system process.command + exit $?.exitstatus || 255 + end + end + + info "started with pid #{pid}", process + running_processes[pid] = process + end + + def kill_and_exit(signal="TERM") + info "termination requested" + running_processes.each do |pid, process| + info "killing pid #{pid}", process + Process.kill(signal, pid) + end + exit 0 + end + + def info(message, process=nil) + puts "[foreman] [#{Time.now.utc}] [#{process ? process.name : "system"}] #{message}" + end + + def print_info + info "currently running processes:" + running_processes.each do |pid, process| + info "pid #{pid}", process + end + end + + def running_processes + @running_processes ||= {} + end + + def proctitle(title) + $0 = title + end + +end diff --git a/lib/foreman/export.rb b/lib/foreman/export.rb new file mode 100644 index 0000000..4aef006 --- /dev/null +++ b/lib/foreman/export.rb @@ -0,0 +1,6 @@ +require "foreman" + +module Foreman::Export +end + +require "foreman/export/upstart" diff --git a/lib/foreman/export/upstart.rb b/lib/foreman/export/upstart.rb new file mode 100644 index 0000000..89d02be --- /dev/null +++ b/lib/foreman/export/upstart.rb @@ -0,0 +1,63 @@ +require "foreman/export" + +class Foreman::Export::Upstart + + attr_reader :engine + + def initialize(engine) + @engine = engine + end + + def export(name) + FileUtils.mkdir_p "/etc/foreman" + FileUtils.mkdir_p "/etc/init" + + write_file "/etc/foreman/#{name}.conf", <<-UPSTART_CONFIG +#{name}_processes="#{engine.processes.keys.join(' ')}" +#{engine.processes.keys.map { |k| "#{name}_#{k}=\"1\"" }.join("\n")} + UPSTART_CONFIG + + write_file "/etc/init/#{name}.conf", <<-UPSTART_MASTER +pre-start script + +bash << "EOF" + mkdir -p /var/log/#{name} + + if [ -f /etc/foreman/#{name}.conf ]; then + source /etc/foreman/#{name}.conf + fi + + for process in $( echo "$#{name}_processes" ); do + process_count_config="#{name}_$process" + process_count=${!process_count_config} + + for ((i=1; i<=${process_count:=1}; i+=1)); do + start #{name}-$process NUM=$i + done + done +EOF + +end script + UPSTART_MASTER + + engine.processes.values.each do |process| + write_file "/etc/init/#{name}-#{process.name}.conf", <<-UPSTART_CHILD +instance $NUM +stop on stopping #{name} +respawn + +chdir #{engine.directory} +exec #{process.command} 2>&1 > /var/log/#{name}/#{process.name}.log + UPSTART_CHILD + end + end + +private + + def write_file(filename, contents) + File.open(filename, "w") do |file| + file.puts contents + end + end + +end diff --git a/lib/foreman/process.rb b/lib/foreman/process.rb new file mode 100644 index 0000000..3014c68 --- /dev/null +++ b/lib/foreman/process.rb @@ -0,0 +1,13 @@ +require "foreman" + +class Foreman::Process + + attr_reader :name + attr_reader :command + + def initialize(name, command) + @name = name + @command = command + end + +end