HeSYINUvSBZfxqA-foreman 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,30 @@
1
+ # Foreman
2
+
3
+ ## Installation
4
+
5
+ gem install foreman
6
+
7
+ ## Description
8
+
9
+ http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
10
+
11
+ ## Manual
12
+
13
+ See the [man page](http://ddollar.github.com/foreman) for usage.
14
+
15
+ ## Authorship
16
+
17
+ Created by David Dollar
18
+
19
+ Patches contributed by:
20
+
21
+ * Adam Wiggins
22
+ * clifff
23
+ * Dan Peterson
24
+ * Jay Zeschin
25
+ * Keith Rarick
26
+ * Ricardo Chimal, Jr
27
+
28
+ ## License
29
+
30
+ MIT
data/bin/foreman ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "foreman/cli"
6
+
7
+ Foreman::CLI.start
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ exec ARGV.join(" ")
@@ -0,0 +1,2 @@
1
+ ticker: ./ticker $PORT
2
+ error : ./error
@@ -0,0 +1,2 @@
1
+ ticker ./ticker $PORT
2
+ error ./error
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ puts "will error in 10s"
4
+ sleep 5
5
+ raise "Dying"
@@ -0,0 +1,4 @@
1
+ tick
2
+ tick
3
+ ./never_die:6:in `sleep': Interrupt
4
+ from ./never_die:6
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ %w( SIGINT SIGTERM SIGHUP ).each do |signal|
4
+ trap(signal) do
5
+ puts "received #{signal} but i'm ignoring it!"
6
+ end
7
+ end
8
+
9
+ while true
10
+ puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
11
+ sleep 1
12
+ end
@@ -0,0 +1,8 @@
1
+ pre-start script
2
+
3
+ bash << "EOF"
4
+ mkdir -p <%= log_root %>
5
+ chown -R <%= user %> <%= log_root %>
6
+ EOF
7
+
8
+ end script
@@ -0,0 +1,5 @@
1
+ start on starting <%= app %>-<%= process.name %>
2
+ stop on stopping <%= app %>-<%= process.name %>
3
+ respawn
4
+
5
+ exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>; <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
@@ -0,0 +1,2 @@
1
+ start on starting <%= app %>
2
+ stop on stopping <%= app %>
data/lib/foreman.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "foreman/version"
2
+
3
+ module Foreman
4
+
5
+ class AppDoesNotExist < Exception; end
6
+
7
+ end
8
+
@@ -0,0 +1,94 @@
1
+ require "foreman"
2
+ require "foreman/engine"
3
+ require "foreman/export"
4
+ require "thor"
5
+ require "yaml"
6
+
7
+ class Foreman::CLI < Thor
8
+
9
+ class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
10
+
11
+ desc "start [PROCESS]", "Start the application, or a specific process"
12
+
13
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
14
+ method_option :port, :type => :numeric, :aliases => "-p"
15
+ method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
16
+
17
+ def start(process=nil)
18
+ check_procfile!
19
+
20
+ if process
21
+ engine.execute(process, options)
22
+ else
23
+ engine.start(options)
24
+ end
25
+ end
26
+
27
+ desc "export FORMAT LOCATION", "Export the application to another process management format"
28
+
29
+ method_option :app, :type => :string, :aliases => "-a"
30
+ method_option :log, :type => :string, :aliases => "-l"
31
+ method_option :port, :type => :numeric, :aliases => "-p"
32
+ method_option :user, :type => :string, :aliases => "-u"
33
+ method_option :template, :type => :string, :aliases => "-t"
34
+ method_option :concurrency, :type => :string, :aliases => "-c",
35
+ :banner => '"alpha=5,bar=3"'
36
+
37
+ def export(format, location=nil)
38
+ check_procfile!
39
+
40
+ formatter = case format
41
+ when "inittab" then Foreman::Export::Inittab
42
+ when "upstart" then Foreman::Export::Upstart
43
+ else error "Unknown export format: #{format}."
44
+ end
45
+
46
+ formatter.new(engine).export(location, options)
47
+
48
+ rescue Foreman::Export::Exception => ex
49
+ error ex.message
50
+ end
51
+
52
+ desc "check", "Validate your application's Procfile"
53
+
54
+ def check
55
+ processes = engine.processes_in_order.map { |p| p.first }
56
+ error "no processes defined" unless processes.length > 0
57
+ display "valid procfile detected (#{processes.join(', ')})"
58
+ end
59
+
60
+ private ######################################################################
61
+
62
+ def check_procfile!
63
+ error("#{procfile} does not exist.") unless File.exist?(procfile)
64
+ end
65
+
66
+ def engine
67
+ @engine ||= Foreman::Engine.new(procfile)
68
+ end
69
+
70
+ def procfile
71
+ options[:procfile] || "Procfile"
72
+ end
73
+
74
+ def display(message)
75
+ puts message
76
+ end
77
+
78
+ def error(message)
79
+ puts "ERROR: #{message}"
80
+ exit 1
81
+ end
82
+
83
+ def procfile_exists?(procfile)
84
+ File.exist?(procfile)
85
+ end
86
+
87
+ def options
88
+ original_options = super
89
+ return original_options unless File.exists?(".foreman")
90
+ defaults = YAML::load_file(".foreman") || {}
91
+ Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
92
+ end
93
+
94
+ end
@@ -0,0 +1,233 @@
1
+ require "foreman"
2
+ require "foreman/process"
3
+ require "foreman/utils"
4
+ require "pty"
5
+ require "tempfile"
6
+ require "timeout"
7
+ require "term/ansicolor"
8
+ require "fileutils"
9
+
10
+ class Foreman::Engine
11
+
12
+ attr_reader :procfile
13
+ attr_reader :directory
14
+
15
+ extend Term::ANSIColor
16
+
17
+ COLORS = [ cyan, yellow, green, magenta, red ]
18
+
19
+ def initialize(procfile)
20
+ @procfile = read_procfile(procfile)
21
+ @directory = File.expand_path(File.dirname(procfile))
22
+ end
23
+
24
+ def processes
25
+ @processes ||= begin
26
+ @order = []
27
+ procfile.split("\n").inject({}) do |hash, line|
28
+ next hash if line.strip == ""
29
+ name, command = line.split(/ *: +/, 2)
30
+ unless command
31
+ warn_deprecated_procfile!
32
+ name, command = line.split(/ +/, 2)
33
+ end
34
+ process = Foreman::Process.new(name, command)
35
+ process.color = next_color
36
+ @order << process.name
37
+ hash.update(process.name => process)
38
+ end
39
+ end
40
+ end
41
+
42
+ def process_order
43
+ processes
44
+ @order.uniq
45
+ end
46
+
47
+ def processes_in_order
48
+ process_order.map do |name|
49
+ [name, processes[name]]
50
+ end
51
+ end
52
+
53
+ def start(options={})
54
+ environment = read_environment(options[:env])
55
+
56
+ proctitle "ruby: foreman master"
57
+
58
+ processes_in_order.each do |name, process|
59
+ fork process, options, environment
60
+ end
61
+
62
+ trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
63
+ trap("INT") { puts "SIGINT received"; terminate_gracefully }
64
+
65
+ watch_for_termination
66
+ end
67
+
68
+ def execute(name, options={})
69
+ environment = read_environment(options[:env])
70
+
71
+ fork processes[name], options, environment
72
+
73
+ trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
74
+ trap("INT") { puts "SIGINT received"; terminate_gracefully }
75
+
76
+ watch_for_termination
77
+ end
78
+
79
+ def port_for(process, num, base_port=nil)
80
+ base_port ||= 5000
81
+ offset = processes_in_order.map { |p| p.first }.index(process.name) * 100
82
+ base_port.to_i + offset + num - 1
83
+ end
84
+
85
+ private ######################################################################
86
+
87
+ def fork(process, options={}, environment={})
88
+ concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
89
+
90
+ 1.upto(concurrency[process.name]) do |num|
91
+ fork_individual(process, num, port_for(process, num, options[:port]), environment)
92
+ end
93
+ end
94
+
95
+ def fork_individual(process, num, port, environment)
96
+ environment.each { |k,v| ENV[k] = v }
97
+
98
+ ENV["PORT"] = port.to_s
99
+ ENV["PS"] = "#{process.name}.#{num}"
100
+
101
+ pid = Process.fork do
102
+ run(process)
103
+ end
104
+
105
+ info "started with pid #{pid}", process
106
+ running_processes[pid] = process
107
+ end
108
+
109
+ def run(process)
110
+ proctitle "ruby: foreman #{process.name}"
111
+ trap("SIGINT", "IGNORE")
112
+
113
+ begin
114
+ Dir.chdir directory do
115
+ PTY.spawn(runner, process.command) do |stdin, stdout, pid|
116
+ trap("SIGTERM") { Process.kill("SIGTERM", pid) }
117
+ until stdin.eof?
118
+ info stdin.gets, process
119
+ end
120
+ end
121
+ end
122
+ rescue PTY::ChildExited, Interrupt, Errno::EIO
123
+ begin
124
+ info "process exiting", process
125
+ rescue Interrupt
126
+ end
127
+ end
128
+ end
129
+
130
+ def kill_all(signal="SIGTERM")
131
+ running_processes.each do |pid, process|
132
+ Process.kill(signal, pid) rescue Errno::ESRCH
133
+ end
134
+ end
135
+
136
+ def info(message, process=nil)
137
+ print process.color if process
138
+ print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
139
+ print Term::ANSIColor.reset
140
+ print message.chomp
141
+ puts
142
+ end
143
+
144
+ def error(message)
145
+ puts "ERROR: #{message}"
146
+ exit 1
147
+ end
148
+
149
+ def longest_process_name
150
+ @longest_process_name ||= begin
151
+ longest = processes.keys.map { |name| name.length }.sort.last
152
+ longest = 6 if longest < 6 # system
153
+ longest
154
+ end
155
+ end
156
+
157
+ def pad_process_name(process)
158
+ name = process ? "#{ENV["PS"]}" : "system"
159
+ name.ljust(longest_process_name + 3) # add 3 for process number padding
160
+ end
161
+
162
+ def print_info
163
+ info "currently running processes:"
164
+ running_processes.each do |pid, process|
165
+ info "pid #{pid}", process
166
+ end
167
+ end
168
+
169
+ def proctitle(title)
170
+ $0 = title
171
+ end
172
+
173
+ def read_procfile(procfile)
174
+ File.read(procfile)
175
+ end
176
+
177
+ def watch_for_termination
178
+ pid, status = Process.wait2
179
+ process = running_processes.delete(pid)
180
+ info "process terminated", process
181
+ terminate_gracefully
182
+ kill_all
183
+ rescue Errno::ECHILD
184
+ end
185
+
186
+ def running_processes
187
+ @running_processes ||= {}
188
+ end
189
+
190
+ def next_color
191
+ @current_color ||= -1
192
+ @current_color += 1
193
+ @current_color >= COLORS.length ? "" : COLORS[@current_color]
194
+ end
195
+
196
+ def warn_deprecated_procfile!
197
+ return if @already_warned_deprecated
198
+ @already_warned_deprecated = true
199
+ puts "!!! This format of Procfile is deprecated, and will not work starting in v0.12"
200
+ puts "!!! Use a colon to separate the process name from the command"
201
+ puts "!!! e.g. web: thin start"
202
+ end
203
+
204
+ def read_environment(filename)
205
+ error "No such file: #{filename}" if filename && !File.exists?(filename)
206
+ filename ||= ".env"
207
+ environment = {}
208
+
209
+ if File.exists?(filename)
210
+ File.read(filename).split("\n").each do |line|
211
+ if line =~ /\A([A-Za-z_]+)=(.*)\z/
212
+ environment[$1] = $2
213
+ end
214
+ end
215
+ end
216
+
217
+ environment
218
+ end
219
+
220
+ def runner
221
+ File.expand_path("../../../bin/foreman-runner", __FILE__)
222
+ end
223
+
224
+ def terminate_gracefully
225
+ info "sending SIGTERM to all processes"
226
+ kill_all "SIGTERM"
227
+ Timeout.timeout(3) { Process.waitall }
228
+ rescue Timeout::Error
229
+ info "sending SIGKILL to all processes"
230
+ kill_all "SIGKILL"
231
+ end
232
+
233
+ end
@@ -0,0 +1,9 @@
1
+ require "foreman"
2
+
3
+ module Foreman::Export
4
+ class Exception < ::Exception; end
5
+ end
6
+
7
+ require "foreman/export/base"
8
+ require "foreman/export/inittab"
9
+ require "foreman/export/upstart"