fiveman 0.1.0

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 (136) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +11 -0
  3. data/bin/fiveman +7 -0
  4. data/bin/fiveman-runner +41 -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/daemon/master.conf.erb +14 -0
  15. data/data/export/daemon/process.conf.erb +8 -0
  16. data/data/export/daemon/process_master.conf.erb +2 -0
  17. data/data/export/launchd/launchd.plist.erb +33 -0
  18. data/data/export/runit/log/run.erb +7 -0
  19. data/data/export/runit/run.erb +4 -0
  20. data/data/export/supervisord/app.conf.erb +31 -0
  21. data/data/export/systemd/master.target.erb +5 -0
  22. data/data/export/systemd/process.service.erb +21 -0
  23. data/data/export/upstart/master.conf.erb +2 -0
  24. data/data/export/upstart/process.conf.erb +15 -0
  25. data/data/export/upstart/process_master.conf.erb +2 -0
  26. data/lib/fiveman/cli.rb +162 -0
  27. data/lib/fiveman/distribution.rb +9 -0
  28. data/lib/fiveman/engine/cli.rb +101 -0
  29. data/lib/fiveman/engine.rb +494 -0
  30. data/lib/fiveman/env.rb +29 -0
  31. data/lib/fiveman/export/base.rb +171 -0
  32. data/lib/fiveman/export/bluepill.rb +12 -0
  33. data/lib/fiveman/export/daemon.rb +28 -0
  34. data/lib/fiveman/export/inittab.rb +42 -0
  35. data/lib/fiveman/export/launchd.rb +22 -0
  36. data/lib/fiveman/export/runit.rb +34 -0
  37. data/lib/fiveman/export/supervisord.rb +16 -0
  38. data/lib/fiveman/export/systemd.rb +34 -0
  39. data/lib/fiveman/export/upstart.rb +46 -0
  40. data/lib/fiveman/export.rb +36 -0
  41. data/lib/fiveman/helpers.rb +45 -0
  42. data/lib/fiveman/process.rb +80 -0
  43. data/lib/fiveman/procfile.rb +94 -0
  44. data/lib/fiveman/vendor/thor/lib/thor/actions/create_file.rb +103 -0
  45. data/lib/fiveman/vendor/thor/lib/thor/actions/create_link.rb +59 -0
  46. data/lib/fiveman/vendor/thor/lib/thor/actions/directory.rb +118 -0
  47. data/lib/fiveman/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
  48. data/lib/fiveman/vendor/thor/lib/thor/actions/file_manipulation.rb +327 -0
  49. data/lib/fiveman/vendor/thor/lib/thor/actions/inject_into_file.rb +103 -0
  50. data/lib/fiveman/vendor/thor/lib/thor/actions.rb +318 -0
  51. data/lib/fiveman/vendor/thor/lib/thor/base.rb +656 -0
  52. data/lib/fiveman/vendor/thor/lib/thor/command.rb +133 -0
  53. data/lib/fiveman/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +85 -0
  54. data/lib/fiveman/vendor/thor/lib/thor/core_ext/io_binary_read.rb +12 -0
  55. data/lib/fiveman/vendor/thor/lib/thor/core_ext/ordered_hash.rb +129 -0
  56. data/lib/fiveman/vendor/thor/lib/thor/error.rb +32 -0
  57. data/lib/fiveman/vendor/thor/lib/thor/group.rb +281 -0
  58. data/lib/fiveman/vendor/thor/lib/thor/invocation.rb +177 -0
  59. data/lib/fiveman/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
  60. data/lib/fiveman/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
  61. data/lib/fiveman/vendor/thor/lib/thor/line_editor.rb +17 -0
  62. data/lib/fiveman/vendor/thor/lib/thor/parser/argument.rb +70 -0
  63. data/lib/fiveman/vendor/thor/lib/thor/parser/arguments.rb +175 -0
  64. data/lib/fiveman/vendor/thor/lib/thor/parser/option.rb +146 -0
  65. data/lib/fiveman/vendor/thor/lib/thor/parser/options.rb +220 -0
  66. data/lib/fiveman/vendor/thor/lib/thor/parser.rb +4 -0
  67. data/lib/fiveman/vendor/thor/lib/thor/rake_compat.rb +71 -0
  68. data/lib/fiveman/vendor/thor/lib/thor/runner.rb +322 -0
  69. data/lib/fiveman/vendor/thor/lib/thor/shell/basic.rb +436 -0
  70. data/lib/fiveman/vendor/thor/lib/thor/shell/color.rb +149 -0
  71. data/lib/fiveman/vendor/thor/lib/thor/shell/html.rb +126 -0
  72. data/lib/fiveman/vendor/thor/lib/thor/shell.rb +81 -0
  73. data/lib/fiveman/vendor/thor/lib/thor/util.rb +268 -0
  74. data/lib/fiveman/vendor/thor/lib/thor/version.rb +3 -0
  75. data/lib/fiveman/vendor/thor/lib/thor.rb +492 -0
  76. data/lib/fiveman/version.rb +5 -0
  77. data/lib/fiveman.rb +17 -0
  78. data/man/fiveman.1 +284 -0
  79. data/spec/fiveman/cli_spec.rb +111 -0
  80. data/spec/fiveman/engine_spec.rb +114 -0
  81. data/spec/fiveman/export/base_spec.rb +19 -0
  82. data/spec/fiveman/export/bluepill_spec.rb +37 -0
  83. data/spec/fiveman/export/daemon_spec.rb +97 -0
  84. data/spec/fiveman/export/inittab_spec.rb +40 -0
  85. data/spec/fiveman/export/launchd_spec.rb +31 -0
  86. data/spec/fiveman/export/runit_spec.rb +36 -0
  87. data/spec/fiveman/export/supervisord_spec.rb +38 -0
  88. data/spec/fiveman/export/systemd_spec.rb +155 -0
  89. data/spec/fiveman/export/upstart_spec.rb +118 -0
  90. data/spec/fiveman/export_spec.rb +24 -0
  91. data/spec/fiveman/helpers_spec.rb +26 -0
  92. data/spec/fiveman/process_spec.rb +71 -0
  93. data/spec/fiveman/procfile_spec.rb +57 -0
  94. data/spec/fiveman_spec.rb +16 -0
  95. data/spec/helper_spec.rb +19 -0
  96. data/spec/resources/Procfile +5 -0
  97. data/spec/resources/Procfile.bad +2 -0
  98. data/spec/resources/bin/echo +2 -0
  99. data/spec/resources/bin/env +2 -0
  100. data/spec/resources/bin/test +2 -0
  101. data/spec/resources/bin/utf8 +2 -0
  102. data/spec/resources/export/bluepill/app-concurrency.pill +49 -0
  103. data/spec/resources/export/bluepill/app.pill +81 -0
  104. data/spec/resources/export/daemon/app-alpha-1.conf +7 -0
  105. data/spec/resources/export/daemon/app-alpha-2.conf +7 -0
  106. data/spec/resources/export/daemon/app-alpha.conf +2 -0
  107. data/spec/resources/export/daemon/app-bravo-1.conf +7 -0
  108. data/spec/resources/export/daemon/app-bravo.conf +2 -0
  109. data/spec/resources/export/daemon/app.conf +14 -0
  110. data/spec/resources/export/inittab/inittab.concurrency +4 -0
  111. data/spec/resources/export/inittab/inittab.default +6 -0
  112. data/spec/resources/export/launchd/launchd-a.default +29 -0
  113. data/spec/resources/export/launchd/launchd-b.default +29 -0
  114. data/spec/resources/export/launchd/launchd-c.default +30 -0
  115. data/spec/resources/export/runit/app-alpha-1/log/run +7 -0
  116. data/spec/resources/export/runit/app-alpha-1/run +4 -0
  117. data/spec/resources/export/runit/app-alpha-2/log/run +7 -0
  118. data/spec/resources/export/runit/app-alpha-2/run +4 -0
  119. data/spec/resources/export/runit/app-bravo-1/log/run +7 -0
  120. data/spec/resources/export/runit/app-bravo-1/run +4 -0
  121. data/spec/resources/export/supervisord/app-alpha-1.conf +42 -0
  122. data/spec/resources/export/supervisord/app-alpha-2.conf +22 -0
  123. data/spec/resources/export/systemd/app-alpha.1.service +18 -0
  124. data/spec/resources/export/systemd/app-alpha.2.service +18 -0
  125. data/spec/resources/export/systemd/app-alpha.target +2 -0
  126. data/spec/resources/export/systemd/app-bravo.1.service +18 -0
  127. data/spec/resources/export/systemd/app-bravo.target +2 -0
  128. data/spec/resources/export/systemd/app.target +5 -0
  129. data/spec/resources/export/upstart/app-alpha-1.conf +11 -0
  130. data/spec/resources/export/upstart/app-alpha-2.conf +11 -0
  131. data/spec/resources/export/upstart/app-alpha.conf +2 -0
  132. data/spec/resources/export/upstart/app-bravo-1.conf +11 -0
  133. data/spec/resources/export/upstart/app-bravo.conf +2 -0
  134. data/spec/resources/export/upstart/app.conf +2 -0
  135. data/spec/spec_helper.rb +177 -0
  136. metadata +177 -0
@@ -0,0 +1,494 @@
1
+ require "fiveman"
2
+ require "fiveman/env"
3
+ require "fiveman/process"
4
+ require "fiveman/procfile"
5
+ require "tempfile"
6
+ require "fileutils"
7
+ require "thread"
8
+
9
+ class Fiveman::Engine
10
+
11
+ # The signals that the engine cares about.
12
+ #
13
+ HANDLED_SIGNALS = [ :TERM, :INT, :HUP, :USR1, :USR2 ]
14
+
15
+ attr_reader :env
16
+ attr_reader :options
17
+ attr_reader :processes
18
+
19
+ # Create an +Engine+ for running processes
20
+ #
21
+ # @param [Hash] options
22
+ #
23
+ # @option options [String] :formation (all=1) The process formation to use
24
+ # @option options [Fixnum] :port (5000) The base port to assign to processes
25
+ # @option options [String] :root (Dir.pwd) The root directory from which to run processes
26
+ #
27
+ def initialize(options={})
28
+ @options = options.dup
29
+
30
+ @options[:formation] ||= "all=1"
31
+ @options[:timeout] ||= 5
32
+
33
+ @env = {}
34
+ @mutex = Mutex.new
35
+ @names = {}
36
+ @processes = []
37
+ @running = {}
38
+ @readers = {}
39
+ @shutdown = false
40
+
41
+ # Self-pipe for deferred signal-handling (ala djb: http://cr.yp.to/docs/selfpipe.html)
42
+ reader, writer = create_pipe
43
+ reader.close_on_exec = true if reader.respond_to?(:close_on_exec)
44
+ writer.close_on_exec = true if writer.respond_to?(:close_on_exec)
45
+ @selfpipe = { :reader => reader, :writer => writer }
46
+
47
+ # Set up a global signal queue
48
+ # http://blog.rubybestpractices.com/posts/ewong/016-Implementing-Signal-Handlers.html
49
+ Thread.main[:signal_queue] = []
50
+ end
51
+
52
+ # Start the processes registered to this +Engine+
53
+ #
54
+ def start
55
+ register_signal_handlers
56
+ startup
57
+ spawn_processes
58
+ watch_for_output
59
+ sleep 0.1
60
+ wait_for_shutdown_or_child_termination
61
+ shutdown
62
+ exit(@exitstatus) if @exitstatus
63
+ end
64
+
65
+ # Set up deferred signal handlers
66
+ #
67
+ def register_signal_handlers
68
+ HANDLED_SIGNALS.each do |sig|
69
+ if ::Signal.list.include? sig.to_s
70
+ trap(sig) { Thread.main[:signal_queue] << sig ; notice_signal }
71
+ end
72
+ end
73
+ end
74
+
75
+ # Unregister deferred signal handlers
76
+ #
77
+ def restore_default_signal_handlers
78
+ HANDLED_SIGNALS.each do |sig|
79
+ trap(sig, :DEFAULT) if ::Signal.list.include? sig.to_s
80
+ end
81
+ end
82
+
83
+ # Wake the main thread up via the selfpipe when there's a signal
84
+ #
85
+ def notice_signal
86
+ @selfpipe[:writer].write_nonblock( '.' )
87
+ rescue Errno::EAGAIN
88
+ # Ignore writes that would block
89
+ rescue Errno::EINTR
90
+ # Retry if another signal arrived while writing
91
+ retry
92
+ end
93
+
94
+ # Invoke the real handler for signal +sig+. This shouldn't be called directly
95
+ # by signal handlers, as it might invoke code which isn't re-entrant.
96
+ #
97
+ # @param [Symbol] sig the name of the signal to be handled
98
+ #
99
+ def handle_signal(sig)
100
+ case sig
101
+ when :TERM
102
+ handle_term_signal
103
+ when :INT
104
+ handle_interrupt
105
+ when :HUP
106
+ handle_hangup
107
+ when *HANDLED_SIGNALS
108
+ handle_signal_forward(sig)
109
+ else
110
+ system "unhandled signal #{sig}"
111
+ end
112
+ end
113
+
114
+ # Handle a TERM signal
115
+ #
116
+ def handle_term_signal
117
+ system "SIGTERM received, starting shutdown"
118
+ @shutdown = true
119
+ end
120
+
121
+ # Handle an INT signal
122
+ #
123
+ def handle_interrupt
124
+ system "SIGINT received, starting shutdown"
125
+ @shutdown = true
126
+ end
127
+
128
+ # Handle a HUP signal
129
+ #
130
+ def handle_hangup
131
+ system "SIGHUP received, starting shutdown"
132
+ @shutdown = true
133
+ end
134
+
135
+ def handle_signal_forward(signal)
136
+ system "#{signal} received, forwarding it to children"
137
+ kill_children signal
138
+ end
139
+
140
+ # Register a process to be run by this +Engine+
141
+ #
142
+ # @param [String] name A name for this process
143
+ # @param [String] command The command to run
144
+ # @param [Hash] options
145
+ #
146
+ # @option options [Hash] :env A custom environment for this process
147
+ #
148
+ def register(name, command, options={})
149
+ options[:env] ||= env
150
+ options[:cwd] ||= File.dirname(command.split(" ").first)
151
+ process = Fiveman::Process.new(command, options)
152
+ @names[process] = name
153
+ @processes << process
154
+ end
155
+
156
+ # Clear the processes registered to this +Engine+
157
+ #
158
+ def clear
159
+ @names = {}
160
+ @processes = []
161
+ end
162
+
163
+ # Register processes by reading a Procfile
164
+ #
165
+ # @param [String] filename A Procfile from which to read processes to register
166
+ #
167
+ def load_procfile(filename)
168
+ options[:root] ||= File.dirname(filename)
169
+ Fiveman::Procfile.new(filename).entries do |name, command|
170
+ register name, command, :cwd => options[:root]
171
+ end
172
+ self
173
+ end
174
+
175
+ # Load a .env file into the +env+ for this +Engine+
176
+ #
177
+ # @param [String] filename A .env file to load into the environment
178
+ #
179
+ def load_env(filename)
180
+ Fiveman::Env.new(filename).entries do |name, value|
181
+ @env[name] = value
182
+ end
183
+ end
184
+
185
+ # Send a signal to all processes started by this +Engine+
186
+ #
187
+ # @param [String] signal The signal to send to each process
188
+ #
189
+ def kill_children(signal="SIGTERM")
190
+ if Fiveman.windows?
191
+ @running.each do |pid, (process, index)|
192
+ system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
193
+ begin
194
+ Process.kill(signal, pid)
195
+ rescue Errno::ESRCH, Errno::EPERM
196
+ end
197
+ end
198
+ else
199
+ begin
200
+ pids = @running.keys.compact
201
+ Process.kill signal, *pids unless pids.empty?
202
+ rescue Errno::ESRCH, Errno::EPERM
203
+ end
204
+ end
205
+ end
206
+
207
+ # Send a signal to the whole process group.
208
+ #
209
+ # @param [String] signal The signal to send
210
+ #
211
+ def killall(signal="SIGTERM")
212
+ if Fiveman.windows?
213
+ kill_children(signal)
214
+ else
215
+ begin
216
+ Process.kill "-#{signal}", Process.pid
217
+ rescue Errno::ESRCH, Errno::EPERM
218
+ end
219
+ end
220
+ end
221
+
222
+ # Get the process formation
223
+ #
224
+ # @returns [Fixnum] The formation count for the specified process
225
+ #
226
+ def formation
227
+ @formation ||= parse_formation(options[:formation])
228
+ end
229
+
230
+ # List the available process names
231
+ #
232
+ # @returns [Array] A list of process names
233
+ #
234
+ def process_names
235
+ @processes.map { |p| @names[p] }
236
+ end
237
+
238
+ # Get the +Process+ for a specifid name
239
+ #
240
+ # @param [String] name The process name
241
+ #
242
+ # @returns [Fiveman::Process] The +Process+ for the specified name
243
+ #
244
+ def process(name)
245
+ @names.invert[name]
246
+ end
247
+
248
+ # Yield each +Process+ in order
249
+ #
250
+ def each_process
251
+ process_names.each do |name|
252
+ yield name, process(name)
253
+ end
254
+ end
255
+
256
+ # Get the root directory for this +Engine+
257
+ #
258
+ # @returns [String] The root directory
259
+ #
260
+ def root
261
+ File.expand_path(options[:root] || Dir.pwd)
262
+ end
263
+
264
+ # Get the port for a given process and offset
265
+ #
266
+ # @param [Fiveman::Process] process A +Process+ associated with this engine
267
+ # @param [Fixnum] instance The instance of the process
268
+ #
269
+ # @returns [Fixnum] port The port to use for this instance of this process
270
+ #
271
+ def port_for(process, instance, base=nil)
272
+ if base
273
+ base + (@processes.index(process.process) * 100) + (instance - 1)
274
+ else
275
+ base_port + (@processes.index(process) * 100) + (instance - 1)
276
+ end
277
+ end
278
+
279
+ # Get the base port for this fiveman instance
280
+ #
281
+ # @returns [Fixnum] port The base port
282
+ #
283
+ def base_port
284
+ (options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
285
+ end
286
+
287
+ # deprecated
288
+ def environment
289
+ env
290
+ end
291
+
292
+ private
293
+
294
+ ### Engine API ######################################################
295
+
296
+ def startup
297
+ raise TypeError, "must use a subclass of Fiveman::Engine"
298
+ end
299
+
300
+ def output(name, data)
301
+ raise TypeError, "must use a subclass of Fiveman::Engine"
302
+ end
303
+
304
+ def shutdown
305
+ raise TypeError, "must use a subclass of Fiveman::Engine"
306
+ end
307
+
308
+ ## Helpers ##########################################################
309
+
310
+ def create_pipe
311
+ IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
312
+ end
313
+
314
+ def name_for(pid)
315
+ process, index = @running[pid]
316
+ name_for_index(process, index)
317
+ end
318
+
319
+ def name_for_index(process, index)
320
+ [ @names[process], index.to_s ].compact.join(".")
321
+ end
322
+
323
+ def parse_formation(formation)
324
+ pairs = formation.to_s.gsub(/\s/, "").split(",")
325
+
326
+ pairs.inject(Hash.new(0)) do |ax, pair|
327
+ process, amount = pair.split("=")
328
+ process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
329
+ ax
330
+ end
331
+ end
332
+
333
+ def output_with_mutex(name, message)
334
+ @mutex.synchronize do
335
+ output name, message
336
+ end
337
+ end
338
+
339
+ def system(message)
340
+ output_with_mutex "system", message
341
+ end
342
+
343
+ def termination_message_for(status)
344
+ if status.exited?
345
+ "exited with code #{status.exitstatus}"
346
+ elsif status.signaled?
347
+ "terminated by SIG#{Signal.list.invert[status.termsig]}"
348
+ else
349
+ "died a mysterious death"
350
+ end
351
+ end
352
+
353
+ def flush_reader(reader)
354
+ until reader.eof?
355
+ data = reader.gets
356
+ output_with_mutex name_for(@readers.key(reader)), data
357
+ end
358
+ end
359
+
360
+ ## Engine ###########################################################
361
+
362
+ def spawn_processes
363
+ @processes.each do |process|
364
+ 1.upto(formation[@names[process]]) do |n|
365
+ reader, writer = create_pipe
366
+ begin
367
+ pid = process.run(:output => writer, :env => {
368
+ "PORT" => port_for(process, n).to_s,
369
+ "PS" => name_for_index(process, n)
370
+ })
371
+ writer.puts "started with pid #{pid}"
372
+ rescue Errno::ENOENT
373
+ writer.puts "unknown command: #{process.command}"
374
+ end
375
+ @running[pid] = [process, n]
376
+ @readers[pid] = reader
377
+ end
378
+ end
379
+ end
380
+
381
+ def read_self_pipe
382
+ @selfpipe[:reader].read_nonblock(11)
383
+ rescue Errno::EAGAIN, Errno::EINTR, Errno::EBADF, Errno::EWOULDBLOCK
384
+ # ignore
385
+ end
386
+
387
+ def handle_signals
388
+ while sig = Thread.main[:signal_queue].shift
389
+ self.handle_signal(sig)
390
+ end
391
+ end
392
+
393
+ def handle_io(readers)
394
+ readers.each do |reader|
395
+ next if reader == @selfpipe[:reader]
396
+
397
+ if reader.eof?
398
+ @readers.delete_if { |key, value| value == reader }
399
+ else
400
+ data = reader.gets
401
+ output_with_mutex name_for(@readers.invert[reader]), data
402
+ end
403
+ end
404
+ end
405
+
406
+ def watch_for_output
407
+ Thread.new do
408
+ begin
409
+ loop do
410
+ io = IO.select([@selfpipe[:reader]] + @readers.values, nil, nil, 30)
411
+ read_self_pipe
412
+ handle_signals
413
+ handle_io(io ? io.first : [])
414
+ end
415
+ rescue Exception => ex
416
+ puts ex.message
417
+ puts ex.backtrace
418
+ end
419
+ end
420
+ end
421
+
422
+ def wait_for_shutdown_or_child_termination
423
+ loop do
424
+ # Stop if it is time to shut down (asked via a signal)
425
+ break if @shutdown
426
+
427
+ # Stop if any of the children died
428
+ break if check_for_termination
429
+
430
+ # Sleep for a moment and do not blow up if any signals are coming our way
431
+ begin
432
+ sleep(1)
433
+ rescue Exception
434
+ # noop
435
+ end
436
+ end
437
+
438
+ # Ok, we have exited from the main loop, time to shut down gracefully
439
+ terminate_gracefully
440
+ end
441
+
442
+ def check_for_termination
443
+ # Check if any of the children have died off
444
+ pid, status = begin
445
+ Process.wait2(-1, Process::WNOHANG)
446
+ rescue Errno::ECHILD
447
+ return nil
448
+ end
449
+
450
+ # record the exit status
451
+ @exitstatus ||= status.exitstatus if status
452
+
453
+ # If no childred have died, nothing to do here
454
+ return nil unless pid
455
+
456
+ # Log the information about the process that exited
457
+ output_with_mutex name_for(pid), termination_message_for(status)
458
+
459
+ # Delete it from the list of running processes and return its pid
460
+ @running.delete(pid)
461
+ return pid
462
+ end
463
+
464
+ def terminate_gracefully
465
+ restore_default_signal_handlers
466
+
467
+ # Tell all children to stop gracefully
468
+ if Fiveman.windows?
469
+ system "sending SIGKILL to all processes"
470
+ kill_children "SIGKILL"
471
+ else
472
+ system "sending SIGTERM to all processes"
473
+ kill_children "SIGTERM"
474
+ end
475
+
476
+ # Wait for all children to stop or until the time comes to kill them all
477
+ start_time = Time.now
478
+ while Time.now - start_time <= options[:timeout]
479
+ return if @running.empty?
480
+ check_for_termination
481
+
482
+ # Sleep for a moment and do not blow up if more signals are coming our way
483
+ begin
484
+ sleep(0.1)
485
+ rescue Exception
486
+ # noop
487
+ end
488
+ end
489
+
490
+ # Ok, we have no other option than to kill all of our children
491
+ system "sending SIGKILL to all processes"
492
+ kill_children "SIGKILL"
493
+ end
494
+ end
@@ -0,0 +1,29 @@
1
+ require "fiveman"
2
+
3
+ class Fiveman::Env
4
+
5
+ attr_reader :entries
6
+
7
+ def initialize(filename)
8
+ @entries = File.read(filename).gsub("\r\n","\n").split("\n").inject({}) do |ax, line|
9
+ if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
10
+ key = $1
11
+ case val = $2
12
+ # Remove single quotes
13
+ when /\A'(.*)'\z/ then ax[key] = $1
14
+ # Remove double quotes and unescape string preserving newline characters
15
+ when /\A"(.*)"\z/ then ax[key] = $1.gsub('\n', "\n").gsub(/\\(.)/, '\1')
16
+ else ax[key] = val
17
+ end
18
+ end
19
+ ax
20
+ end
21
+ end
22
+
23
+ def entries
24
+ @entries.each do |key, value|
25
+ yield key, value
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,171 @@
1
+ require "fiveman/export"
2
+ require "ostruct"
3
+ require "pathname"
4
+ require "shellwords"
5
+
6
+ class Fiveman::Export::Base
7
+
8
+ attr_reader :location
9
+ attr_reader :engine
10
+ attr_reader :options
11
+ attr_reader :formation
12
+
13
+ # deprecated
14
+ attr_reader :port
15
+
16
+ def initialize(location, engine, options={})
17
+ @location = location
18
+ @engine = engine
19
+ @options = options.dup
20
+ @formation = engine.formation
21
+
22
+ # deprecated
23
+ def port
24
+ Fiveman::Export::Base.warn_deprecation!
25
+ engine.base_port
26
+ end
27
+
28
+ # deprecated
29
+ def template
30
+ Fiveman::Export::Base.warn_deprecation!
31
+ options[:template]
32
+ end
33
+
34
+ # deprecated
35
+ def @engine.procfile
36
+ Fiveman::Export::Base.warn_deprecation!
37
+ @processes.map do |process|
38
+ OpenStruct.new(
39
+ :name => @names[process],
40
+ :process => process
41
+ )
42
+ end
43
+ end
44
+ end
45
+
46
+ def export
47
+ error("Must specify a location") unless location
48
+ FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
49
+ chown user, log
50
+ chown user, run
51
+ end
52
+
53
+ def app
54
+ options[:app] || "app"
55
+ end
56
+
57
+ def log
58
+ options[:log] || "/var/log/#{app}"
59
+ end
60
+
61
+ def run
62
+ options[:run] || "/var/run/#{app}"
63
+ end
64
+
65
+ def user
66
+ options[:user] || app
67
+ end
68
+
69
+ private ######################################################################
70
+
71
+ def self.warn_deprecation!
72
+ @@deprecation_warned ||= false
73
+ return if @@deprecation_warned
74
+ puts "WARNING: Using deprecated exporter interface. Please update your exporter"
75
+ puts "the interface shown in the upstart exporter:"
76
+ puts
77
+ puts "https://github.com/ddollar/fiveman/blob/master/lib/fiveman/export/upstart.rb"
78
+ puts "https://github.com/ddollar/fiveman/blob/master/data/export/upstart/process.conf.erb"
79
+ puts
80
+ @@deprecation_warned = true
81
+ end
82
+
83
+ def chown user, dir
84
+ FileUtils.chown user, nil, dir
85
+ rescue
86
+ error("Could not chown #{dir} to #{user}") unless File.writable?(dir) || ! File.exists?(dir)
87
+ end
88
+
89
+ def error(message)
90
+ raise Fiveman::Export::Exception.new(message)
91
+ end
92
+
93
+ def say(message)
94
+ puts "[fiveman export] %s" % message
95
+ end
96
+
97
+ def clean(filename)
98
+ return unless File.exists?(filename)
99
+ say "cleaning up: #{filename}"
100
+ FileUtils.rm(filename)
101
+ end
102
+
103
+ def clean_dir(dirname)
104
+ return unless File.exists?(dirname)
105
+ say "cleaning up directory: #{dirname}"
106
+ FileUtils.rm_r(dirname)
107
+ end
108
+
109
+ def shell_quote(value)
110
+ Shellwords.escape(value)
111
+ end
112
+
113
+ # deprecated
114
+ def old_export_template(exporter, file, template_root)
115
+ if template_root && File.exist?(file_path = File.join(template_root, file))
116
+ File.read(file_path)
117
+ elsif File.exist?(file_path = File.expand_path(File.join("~/.fiveman/templates", file)))
118
+ File.read(file_path)
119
+ else
120
+ File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
121
+ end
122
+ end
123
+
124
+ def export_template(name, file=nil, template_root=nil)
125
+ if file && template_root
126
+ old_export_template name, file, template_root
127
+ else
128
+ name_without_first = name.split("/")[1..-1].join("/")
129
+ matchers = []
130
+ matchers << File.join(options[:template], name_without_first) if options[:template]
131
+ matchers << File.expand_path("~/.fiveman/templates/#{name}")
132
+ matchers << File.expand_path("../../../../data/export/#{name}", __FILE__)
133
+ File.read(matchers.detect { |m| File.exists?(m) })
134
+ end
135
+ end
136
+
137
+ def write_template(name, target, binding)
138
+ compiled = if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
139
+ ERB.new(export_template(name), trim_mode: '-').result(binding)
140
+ else
141
+ ERB.new(export_template(name), nil, '-').result(binding)
142
+ end
143
+ write_file target, compiled
144
+ end
145
+
146
+ def chmod(mode, file)
147
+ say "setting #{file} to mode #{mode}"
148
+ FileUtils.chmod mode, File.join(location, file)
149
+ end
150
+
151
+ def create_directory(dir)
152
+ say "creating: #{dir}"
153
+ FileUtils.mkdir_p(File.join(location, dir))
154
+ end
155
+
156
+ def create_symlink(link, target)
157
+ say "symlinking: #{link} -> #{target}"
158
+ FileUtils.symlink(target, File.join(location, link))
159
+ end
160
+
161
+ def write_file(filename, contents)
162
+ say "writing: #{filename}"
163
+
164
+ filename = File.join(location, filename) unless Pathname.new(filename).absolute?
165
+
166
+ File.open(filename, "w") do |file|
167
+ file.puts contents
168
+ end
169
+ end
170
+
171
+ end
@@ -0,0 +1,12 @@
1
+ require "erb"
2
+ require "fiveman/export"
3
+
4
+ class Fiveman::Export::Bluepill < Fiveman::Export::Base
5
+
6
+ def export
7
+ super
8
+ clean "#{location}/#{app}.pill"
9
+ write_template "bluepill/master.pill.erb", "#{app}.pill", binding
10
+ end
11
+
12
+ end