From d26ca669a15e9a853236082fd76d9e4018d42c1d Mon Sep 17 00:00:00 2001 From: David Dollar Date: Tue, 4 Oct 2011 10:38:13 -0400 Subject: [PATCH] define procfile regex, refactoring --- data/example/Procfile | 2 +- data/export/bluepill/master.pill.erb | 4 +- lib/foreman/cli.rb | 5 +- lib/foreman/engine.rb | 101 ++++++++------------------- lib/foreman/export/upstart.rb | 2 +- lib/foreman/procfile.rb | 37 ++++++++++ spec/foreman/engine_spec.rb | 30 +++----- 7 files changed, 80 insertions(+), 101 deletions(-) create mode 100644 lib/foreman/procfile.rb diff --git a/data/example/Procfile b/data/example/Procfile index c200d64..7e64be2 100644 --- a/data/example/Procfile +++ b/data/example/Procfile @@ -1,2 +1,2 @@ ticker: ruby ./ticker $PORT -error : ruby ./error +error: ruby ./error diff --git a/data/export/bluepill/master.pill.erb b/data/export/bluepill/master.pill.erb index e02785f..fbe11e1 100644 --- a/data/export/bluepill/master.pill.erb +++ b/data/export/bluepill/master.pill.erb @@ -3,7 +3,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/ app.uid = "<%= user %>" app.gid = "<%= user %>" -<% engine.processes.values.each do |process| %> +<% engine.processes.each do |process| %> <% 1.upto(concurrency[process.name]) do |num| %> <% port = engine.port_for(process, num, options[:port]) %> app.process("<%= process.name %>-<%=num%>") do |process| @@ -24,4 +24,4 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/ end <% end %> <% end %> -end \ No newline at end of file +end diff --git a/lib/foreman/cli.rb b/lib/foreman/cli.rb index cea8e7b..52864d6 100644 --- a/lib/foreman/cli.rb +++ b/lib/foreman/cli.rb @@ -53,9 +53,8 @@ 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.processes.length > 0 + display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})" end private ###################################################################### diff --git a/lib/foreman/engine.rb b/lib/foreman/engine.rb index cec3f61..7ba254d 100644 --- a/lib/foreman/engine.rb +++ b/lib/foreman/engine.rb @@ -1,5 +1,6 @@ require "foreman" require "foreman/process" +require "foreman/procfile" require "foreman/utils" require "pty" require "tempfile" @@ -19,45 +20,17 @@ class Foreman::Engine COLORS = [ cyan, yellow, green, magenta, red ] def initialize(procfile, options={}) - @procfile = read_procfile(procfile) + @procfile = Foreman::Procfile.new(procfile) @directory = File.expand_path(File.dirname(procfile)) @options = options @environment = read_environment_files(options[:env]) end - def processes - @processes ||= begin - @order = [] - procfile.split("\n").inject({}) do |hash, line| - next hash if line.strip == "" - name, command = line.split(/\s*:\s+/, 2) - unless command - warn_deprecated_procfile! - name, command = line.split(/ +/, 2) - end - process = Foreman::Process.new(name, command) - process.color = next_color - @order << process.name - hash.update(process.name => process) - end - end - end - - def process_order - processes - @order.uniq - end - - def processes_in_order - process_order.map do |name| - [name, processes[name]] - end - end - def start proctitle "ruby: foreman master" - processes_in_order.each do |name, process| + processes.each do |process| + process.color = next_color fork process end @@ -68,8 +41,7 @@ class Foreman::Engine end def execute(name) - - fork processes[name] + fork procfile[name] trap("TERM") { puts "SIGTERM received"; terminate_gracefully } trap("INT") { puts "SIGINT received"; terminate_gracefully } @@ -77,9 +49,13 @@ class Foreman::Engine watch_for_termination end + def processes + procfile.processes + 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 @@ -134,6 +110,24 @@ private ###################################################################### end end + def terminate_gracefully + info "sending SIGTERM to all processes" + kill_all "SIGTERM" + Timeout.timeout(3) { Process.waitall } + rescue Timeout::Error + info "sending SIGKILL to all processes" + kill_all "SIGKILL" + end + + def watch_for_termination + pid, status = Process.wait2 + process = running_processes.delete(pid) + info "process terminated", process + terminate_gracefully + kill_all + rescue Errno::ECHILD + end + def info(message, process=nil) print process.color if process print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | " @@ -149,7 +143,7 @@ private ###################################################################### 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 @@ -160,30 +154,10 @@ private ###################################################################### name.ljust(longest_process_name + 3) # add 3 for process number padding end - def print_info - info "currently running processes:" - running_processes.each do |pid, process| - info "pid #{pid}", process - end - end - 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 - terminate_gracefully - kill_all - rescue Errno::ECHILD - end - def running_processes @running_processes ||= {} end @@ -194,14 +168,6 @@ private ###################################################################### @current_color >= COLORS.length ? "" : 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" - end - def read_environment_files(filenames) environment = {} @@ -225,13 +191,4 @@ private ###################################################################### end end - def terminate_gracefully - info "sending SIGTERM to all processes" - kill_all "SIGTERM" - Timeout.timeout(3) { Process.waitall } - rescue Timeout::Error - info "sending SIGKILL to all processes" - kill_all "SIGKILL" - end - end diff --git a/lib/foreman/export/upstart.rb b/lib/foreman/export/upstart.rb index 6e2979e..d783505 100644 --- a/lib/foreman/export/upstart.rb +++ b/lib/foreman/export/upstart.rb @@ -26,7 +26,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base process_template = export_template("upstart", "process.conf.erb", template_root) - engine.processes.values.each do |process| + engine.processes.each do |process| 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 diff --git a/lib/foreman/procfile.rb b/lib/foreman/procfile.rb new file mode 100644 index 0000000..f169e4a --- /dev/null +++ b/lib/foreman/procfile.rb @@ -0,0 +1,37 @@ +require "foreman" + +# 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 :processes + + def initialize(filename) + @processes = parse_procfile(filename) + end + + def process_names + processes.map(&:name) + end + + def [](name) + processes.detect { |process| process.name == name } + end + +private + + def parse_procfile(filename) + File.read(filename).split("\n").map do |line| + if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/ + Foreman::Process.new($1, $2) + end + end.compact + end + +end diff --git a/spec/foreman/engine_spec.rb b/spec/foreman/engine_spec.rb index 74022fe..7a20fc2 100644 --- a/spec/foreman/engine_spec.rb +++ b/spec/foreman/engine_spec.rb @@ -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,8 +24,8 @@ 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(subject).fork(subject.procfile["alpha"]) + mock(subject).fork(subject.procfile["bravo"]) mock(subject).watch_for_termination subject.start end @@ -46,9 +33,9 @@ describe "Foreman::Engine" do it "handles concurrency" do write_procfile engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2") - mock(engine).fork_individual(engine.processes["alpha"], 1, 5000) - mock(engine).fork_individual(engine.processes["alpha"], 2, 5001) - mock(engine).fork_individual(engine.processes["bravo"], 1, 5100) + mock(engine).fork_individual(engine.procfile["alpha"], 1, 5000) + mock(engine).fork_individual(engine.procfile["alpha"], 2, 5001) + mock(engine).fork_individual(engine.procfile["bravo"], 1, 5100) mock(engine).watch_for_termination engine.start end @@ -57,14 +44,13 @@ describe "Foreman::Engine" do describe "execute" do it "runs the processes" do write_procfile - mock(subject).fork(subject.processes["alpha"]) + mock(subject).fork(subject.procfile["alpha"]) mock(subject).watch_for_termination subject.execute("alpha") end end describe "environment" do - before(:each) do write_procfile stub(Process).fork