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