foreman 0.50.0-x86-mswin32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/README.md +46 -0
  2. data/bin/foreman +7 -0
  3. data/bin/foreman-runner +32 -0
  4. data/bin/taskman +8 -0
  5. data/data/example/Procfile +4 -0
  6. data/data/example/Procfile.without_colon +2 -0
  7. data/data/example/error +7 -0
  8. data/data/example/log/neverdie.log +4 -0
  9. data/data/example/spawnee +14 -0
  10. data/data/example/spawner +7 -0
  11. data/data/example/ticker +14 -0
  12. data/data/example/utf8 +11 -0
  13. data/data/export/bluepill/master.pill.erb +28 -0
  14. data/data/export/launchd/launchd.plist.erb +22 -0
  15. data/data/export/runit/log/run.erb +7 -0
  16. data/data/export/runit/run.erb +3 -0
  17. data/data/export/supervisord/app.conf.erb +27 -0
  18. data/data/export/upstart/master.conf.erb +8 -0
  19. data/data/export/upstart/process.conf.erb +5 -0
  20. data/data/export/upstart/process_master.conf.erb +2 -0
  21. data/lib/foreman.rb +23 -0
  22. data/lib/foreman/cli.rb +140 -0
  23. data/lib/foreman/distribution.rb +9 -0
  24. data/lib/foreman/engine.rb +313 -0
  25. data/lib/foreman/engine/cli.rb +105 -0
  26. data/lib/foreman/env.rb +27 -0
  27. data/lib/foreman/export.rb +34 -0
  28. data/lib/foreman/export/base.rb +146 -0
  29. data/lib/foreman/export/bluepill.rb +12 -0
  30. data/lib/foreman/export/inittab.rb +33 -0
  31. data/lib/foreman/export/launchd.rb +15 -0
  32. data/lib/foreman/export/runit.rb +34 -0
  33. data/lib/foreman/export/supervisord.rb +16 -0
  34. data/lib/foreman/export/upstart.rb +25 -0
  35. data/lib/foreman/helpers.rb +45 -0
  36. data/lib/foreman/process.rb +102 -0
  37. data/lib/foreman/procfile.rb +92 -0
  38. data/lib/foreman/version.rb +5 -0
  39. data/man/foreman.1 +244 -0
  40. data/spec/foreman/cli_spec.rb +87 -0
  41. data/spec/foreman/engine_spec.rb +104 -0
  42. data/spec/foreman/export/base_spec.rb +19 -0
  43. data/spec/foreman/export/bluepill_spec.rb +37 -0
  44. data/spec/foreman/export/inittab_spec.rb +40 -0
  45. data/spec/foreman/export/launchd_spec.rb +21 -0
  46. data/spec/foreman/export/runit_spec.rb +36 -0
  47. data/spec/foreman/export/supervisord_spec.rb +36 -0
  48. data/spec/foreman/export/upstart_spec.rb +88 -0
  49. data/spec/foreman/export_spec.rb +24 -0
  50. data/spec/foreman/helpers_spec.rb +26 -0
  51. data/spec/foreman/process_spec.rb +48 -0
  52. data/spec/foreman/procfile_spec.rb +41 -0
  53. data/spec/foreman_spec.rb +16 -0
  54. data/spec/helper_spec.rb +18 -0
  55. data/spec/resources/Procfile +4 -0
  56. data/spec/resources/bin/echo +2 -0
  57. data/spec/resources/bin/env +2 -0
  58. data/spec/resources/bin/test +2 -0
  59. data/spec/resources/bin/utf8 +2 -0
  60. data/spec/resources/export/bluepill/app-concurrency.pill +49 -0
  61. data/spec/resources/export/bluepill/app.pill +46 -0
  62. data/spec/resources/export/inittab/inittab.concurrency +4 -0
  63. data/spec/resources/export/inittab/inittab.default +4 -0
  64. data/spec/resources/export/launchd/launchd-a.default +22 -0
  65. data/spec/resources/export/launchd/launchd-b.default +22 -0
  66. data/spec/resources/export/runit/app-alpha-1/log/run +7 -0
  67. data/spec/resources/export/runit/app-alpha-1/run +3 -0
  68. data/spec/resources/export/runit/app-alpha-2/log/run +7 -0
  69. data/spec/resources/export/runit/app-alpha-2/run +3 -0
  70. data/spec/resources/export/runit/app-bravo-1/log/run +7 -0
  71. data/spec/resources/export/runit/app-bravo-1/run +3 -0
  72. data/spec/resources/export/supervisord/app-alpha-1.conf +24 -0
  73. data/spec/resources/export/supervisord/app-alpha-2.conf +24 -0
  74. data/spec/resources/export/upstart/app-alpha-1.conf +5 -0
  75. data/spec/resources/export/upstart/app-alpha-2.conf +5 -0
  76. data/spec/resources/export/upstart/app-alpha.conf +2 -0
  77. data/spec/resources/export/upstart/app-bravo-1.conf +5 -0
  78. data/spec/resources/export/upstart/app-bravo.conf +2 -0
  79. data/spec/resources/export/upstart/app.conf +8 -0
  80. data/spec/spec_helper.rb +153 -0
  81. metadata +148 -0
@@ -0,0 +1,9 @@
1
+ module Foreman
2
+ module Distribution
3
+ def self.files
4
+ Dir[File.expand_path("../../../{bin,data,lib}/**/*", __FILE__)].select do |file|
5
+ File.file?(file)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,313 @@
1
+ require "foreman"
2
+ require "foreman/env"
3
+ require "foreman/process"
4
+ require "foreman/procfile"
5
+ require "tempfile"
6
+ require "timeout"
7
+ require "fileutils"
8
+ require "thread"
9
+
10
+ class Foreman::Engine
11
+
12
+ attr_reader :env
13
+ attr_reader :options
14
+ attr_reader :processes
15
+
16
+ # Create an +Engine+ for running processes
17
+ #
18
+ # @param [Hash] options
19
+ #
20
+ # @option options [String] :formation (all=1) The process formation to use
21
+ # @option options [Fixnum] :port (5000) The base port to assign to processes
22
+ # @option options [String] :root (Dir.pwd) The root directory from which to run processes
23
+ #
24
+ def initialize(options={})
25
+ @options = options.dup
26
+
27
+ @options[:formation] ||= (options[:concurrency] || "all=1")
28
+
29
+ @env = {}
30
+ @mutex = Mutex.new
31
+ @names = {}
32
+ @processes = []
33
+ @running = {}
34
+ @readers = {}
35
+ end
36
+
37
+ # Start the processes registered to this +Engine+
38
+ #
39
+ def start
40
+ trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
41
+ trap("INT") { puts "SIGINT received"; terminate_gracefully }
42
+ trap("HUP") { puts "SIGHUP received"; terminate_gracefully } if ::Signal.list.keys.include? 'HUP'
43
+
44
+ startup
45
+ spawn_processes
46
+ watch_for_output
47
+ sleep 0.1
48
+ watch_for_termination { terminate_gracefully }
49
+ shutdown
50
+ end
51
+
52
+ # Register a process to be run by this +Engine+
53
+ #
54
+ # @param [String] name A name for this process
55
+ # @param [String] command The command to run
56
+ # @param [Hash] options
57
+ #
58
+ # @option options [Hash] :env A custom environment for this process
59
+ #
60
+ def register(name, command, options={})
61
+ options[:env] ||= env
62
+ options[:cwd] ||= File.dirname(command.split(" ").first)
63
+ process = Foreman::Process.new(command, options)
64
+ @names[process] = name
65
+ @processes << process
66
+ end
67
+
68
+ # Clear the processes registered to this +Engine+
69
+ #
70
+ def clear
71
+ @names = {}
72
+ @processes = []
73
+ end
74
+
75
+ # Register processes by reading a Procfile
76
+ #
77
+ # @param [String] filename A Procfile from which to read processes to register
78
+ #
79
+ def load_procfile(filename)
80
+ options[:root] ||= File.dirname(filename)
81
+ Foreman::Procfile.new(filename).entries do |name, command|
82
+ register name, command, :cwd => options[:root]
83
+ end
84
+ self
85
+ end
86
+
87
+ # Load a .env file into the +env+ for this +Engine+
88
+ #
89
+ # @param [String] filename A .env file to load into the environment
90
+ #
91
+ def load_env(filename)
92
+ Foreman::Env.new(filename).entries do |name, value|
93
+ @env[name] = value
94
+ end
95
+ end
96
+
97
+ # Send a signal to all processesstarted by this +Engine+
98
+ #
99
+ # @param [String] signal The signal to send to each process
100
+ #
101
+ def killall(signal="SIGTERM")
102
+ if Foreman.windows?
103
+ @running.each do |pid, (process, index)|
104
+ system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
105
+ begin
106
+ Process.kill(signal, pid)
107
+ rescue Errno::ESRCH, Errno::EPERM
108
+ end
109
+ end
110
+ else
111
+ begin
112
+ Process.kill "-#{signal}", Process.pid
113
+ rescue Errno::ESRCH, Errno::EPERM
114
+ end
115
+ end
116
+ end
117
+
118
+ # Get the process formation
119
+ #
120
+ # @returns [Fixnum] The formation count for the specified process
121
+ #
122
+ def formation
123
+ @formation ||= parse_formation(options[:formation])
124
+ end
125
+
126
+ # List the available process names
127
+ #
128
+ # @returns [Array] A list of process names
129
+ #
130
+ def process_names
131
+ @processes.map { |p| @names[p] }
132
+ end
133
+
134
+ # Get the +Process+ for a specifid name
135
+ #
136
+ # @param [String] name The process name
137
+ #
138
+ # @returns [Foreman::Process] The +Process+ for the specified name
139
+ #
140
+ def process(name)
141
+ @names.invert[name]
142
+ end
143
+
144
+ # Yield each +Process+ in order
145
+ #
146
+ def each_process
147
+ process_names.each do |name|
148
+ yield name, process(name)
149
+ end
150
+ end
151
+
152
+ # Get the root directory for this +Engine+
153
+ #
154
+ # @returns [String] The root directory
155
+ #
156
+ def root
157
+ File.expand_path(options[:root] || Dir.pwd)
158
+ end
159
+
160
+ # Get the port for a given process and offset
161
+ #
162
+ # @param [Foreman::Process] process A +Process+ associated with this engine
163
+ # @param [Fixnum] instance The instance of the process
164
+ #
165
+ # @returns [Fixnum] port The port to use for this instance of this process
166
+ #
167
+ def port_for(process, instance, base=nil)
168
+ if base
169
+ base + (@processes.index(process.process) * 100) + (instance - 1)
170
+ else
171
+ base_port + (@processes.index(process) * 100) + (instance - 1)
172
+ end
173
+ end
174
+
175
+ # Get the base port for this foreman instance
176
+ #
177
+ # @returns [Fixnum] port The base port
178
+ #
179
+ def base_port
180
+ (options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
181
+ end
182
+
183
+ # deprecated
184
+ def environment
185
+ env
186
+ end
187
+
188
+ private
189
+
190
+ ### Engine API ######################################################
191
+
192
+ def startup
193
+ raise TypeError, "must use a subclass of Foreman::Engine"
194
+ end
195
+
196
+ def output(name, data)
197
+ raise TypeError, "must use a subclass of Foreman::Engine"
198
+ end
199
+
200
+ def shutdown
201
+ raise TypeError, "must use a subclass of Foreman::Engine"
202
+ end
203
+
204
+ ## Helpers ##########################################################
205
+
206
+ def create_pipe
207
+ IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
208
+ end
209
+
210
+ def name_for(pid)
211
+ process, index = @running[pid]
212
+ [ @names[process], index.to_s ].compact.join(".")
213
+ end
214
+
215
+ def parse_formation(formation)
216
+ pairs = formation.to_s.gsub(/\s/, "").split(",")
217
+
218
+ pairs.inject(Hash.new(0)) do |ax, pair|
219
+ process, amount = pair.split("=")
220
+ process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
221
+ ax
222
+ end
223
+ end
224
+
225
+ def output_with_mutex(name, message)
226
+ @mutex.synchronize do
227
+ output name, message
228
+ end
229
+ end
230
+
231
+ def system(message)
232
+ output_with_mutex "system", message
233
+ end
234
+
235
+ def termination_message_for(status)
236
+ if status.exited?
237
+ "exited with code #{status.exitstatus}"
238
+ elsif status.signaled?
239
+ "terminated by SIG#{Signal.list.invert[status.termsig]}"
240
+ else
241
+ "died a mysterious death"
242
+ end
243
+ end
244
+
245
+ def flush_reader(reader)
246
+ until reader.eof?
247
+ data = reader.gets
248
+ output_with_mutex name_for(@readers.key(reader)), data
249
+ end
250
+ end
251
+
252
+ ## Engine ###########################################################
253
+
254
+ def spawn_processes
255
+ @processes.each do |process|
256
+ 1.upto(formation[@names[process]]) do |n|
257
+ reader, writer = create_pipe
258
+ begin
259
+ pid = process.run(:output => writer, :env => { "PORT" => port_for(process, n).to_s })
260
+ writer.puts "started with pid #{pid}"
261
+ rescue Errno::ENOENT
262
+ writer.puts "unknown command: #{process.command}"
263
+ end
264
+ @running[pid] = [process, n]
265
+ @readers[pid] = reader
266
+ end
267
+ end
268
+ end
269
+
270
+ def watch_for_output
271
+ Thread.new do
272
+ begin
273
+ loop do
274
+ (IO.select(@readers.values).first || []).each do |reader|
275
+ data = reader.gets
276
+ output_with_mutex name_for(@readers.invert[reader]), data
277
+ end
278
+ end
279
+ rescue Exception => ex
280
+ puts ex.message
281
+ puts ex.backtrace
282
+ end
283
+ end
284
+ end
285
+
286
+ def watch_for_termination
287
+ pid, status = Process.wait2
288
+ output_with_mutex name_for(pid), termination_message_for(status)
289
+ @running.delete(pid)
290
+ yield if block_given?
291
+ pid
292
+ rescue Errno::ECHILD
293
+ end
294
+
295
+ def terminate_gracefully
296
+ return if @terminating
297
+ @terminating = true
298
+ if Foreman.windows?
299
+ system "sending SIGKILL to all processes"
300
+ killall "SIGKILL"
301
+ else
302
+ system "sending SIGTERM to all processes"
303
+ killall "SIGTERM"
304
+ end
305
+ Timeout.timeout(5) do
306
+ watch_for_termination while @running.length > 0
307
+ end
308
+ rescue Timeout::Error
309
+ system "sending SIGKILL to all processes"
310
+ killall "SIGKILL"
311
+ end
312
+
313
+ end
@@ -0,0 +1,105 @@
1
+ require "foreman/engine"
2
+
3
+ class Foreman::Engine::CLI < Foreman::Engine
4
+
5
+ module Color
6
+
7
+ ANSI = {
8
+ :reset => 0,
9
+ :black => 30,
10
+ :red => 31,
11
+ :green => 32,
12
+ :yellow => 33,
13
+ :blue => 34,
14
+ :magenta => 35,
15
+ :cyan => 36,
16
+ :white => 37,
17
+ :bright_black => 30,
18
+ :bright_red => 31,
19
+ :bright_green => 32,
20
+ :bright_yellow => 33,
21
+ :bright_blue => 34,
22
+ :bright_magenta => 35,
23
+ :bright_cyan => 36,
24
+ :bright_white => 37,
25
+ }
26
+
27
+ def self.enable(io, force=false)
28
+ io.extend(self)
29
+ @@color_force = force
30
+ end
31
+
32
+ def color?
33
+ return true if @@color_force
34
+ return true if Foreman.windows?
35
+ return false unless self.respond_to?(:isatty)
36
+ self.isatty && ENV["TERM"]
37
+ end
38
+
39
+ def color(name)
40
+ return "" unless color?
41
+ return "" unless ansi = ANSI[name.to_sym]
42
+ "\e[#{ansi}m"
43
+ end
44
+
45
+ end
46
+
47
+ FOREMAN_COLORS = %w( cyan yellow green magenta red blue intense_cyan intense_yellow
48
+ intense_green intense_magenta intense_red, intense_blue )
49
+
50
+ def startup
51
+ @colors = map_colors
52
+ proctitle "foreman: master"
53
+ require "win32console" if Foreman.windows?
54
+ Color.enable($stdout, options[:color]) unless $stdout.respond_to?(:color?)
55
+ end
56
+
57
+ def output(name, data)
58
+ data.to_s.chomp.split("\n").each do |message|
59
+ output = ""
60
+ output += $stdout.color(@colors[name.split(".").first].to_sym)
61
+ output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
62
+ output += $stdout.color(:reset)
63
+ output += message
64
+ $stdout.puts output
65
+ $stdout.flush
66
+ end
67
+ rescue Errno::EPIPE
68
+ terminate_gracefully
69
+ end
70
+
71
+ def shutdown
72
+ end
73
+
74
+ private
75
+
76
+ def name_padding
77
+ @name_padding ||= begin
78
+ index_padding = @names.values.map { |n| formation[n] }.max.to_s.length + 1
79
+ name_padding = @names.values.map { |n| n.length + index_padding }.sort.last
80
+ [ 6, name_padding ].max
81
+ end
82
+ end
83
+
84
+ def pad_process_name(name)
85
+ name.ljust(name_padding, " ")
86
+ end
87
+
88
+ def map_colors
89
+ colors = Hash.new("white")
90
+ @names.values.each_with_index do |name, index|
91
+ colors[name] = FOREMAN_COLORS[index % FOREMAN_COLORS.length]
92
+ end
93
+ colors["system"] = "intense_white"
94
+ colors
95
+ end
96
+
97
+ def proctitle(title)
98
+ $0 = title
99
+ end
100
+
101
+ def termtitle(title)
102
+ printf("\033]0;#{title}\007") unless Foreman.windows?
103
+ end
104
+
105
+ end
@@ -0,0 +1,27 @@
1
+ require "foreman"
2
+
3
+ class Foreman::Env
4
+
5
+ attr_reader :entries
6
+
7
+ def initialize(filename)
8
+ @entries = File.read(filename).split("\n").inject({}) do |ax, line|
9
+ if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
10
+ key = $1
11
+ case val = $2
12
+ when /\A'(.*)'\z/ then ax[key] = $1
13
+ when /\A"(.*)"\z/ then ax[key] = $1.gsub(/\\(.)/, '\1')
14
+ else ax[key] = val
15
+ end
16
+ end
17
+ ax
18
+ end
19
+ end
20
+
21
+ def entries
22
+ @entries.each do |key, value|
23
+ yield key, value
24
+ end
25
+ end
26
+
27
+ end