procodile 1.0.10 → 1.0.11
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.
- checksums.yaml +4 -4
- data/bin/procodile +85 -20
- data/lib/procodile.rb +9 -0
- data/lib/procodile/cli.rb +96 -31
- data/lib/procodile/config.rb +28 -7
- data/lib/procodile/control_session.rb +7 -0
- data/lib/procodile/instance.rb +64 -10
- data/lib/procodile/process.rb +18 -2
- data/lib/procodile/signal_handler.rb +1 -0
- data/lib/procodile/status_cli_output.rb +4 -4
- data/lib/procodile/supervisor.rb +13 -3
- data/lib/procodile/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90d57673d57a817411b10a60ebbaa04b8222f644
|
4
|
+
data.tar.gz: a928bbe14abe7ad22dee7e98763ef42d12abffb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 227c83421e688d37e8fc544959ead123ca7c1abb5e0f00c465525a7298225a4b3af000eba4b1041ee57ffbba1cb91a7e7332457e35def291697b01dd510436a0
|
7
|
+
data.tar.gz: ce6efd84d43fc76f094ef4f8788c7848d3992548f12493ea529aa0321ec0819c5e9d752a02451227ac57833542e435173aa574136f33beb2a40743cc2b12af55
|
data/bin/procodile
CHANGED
@@ -1,26 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
global_config_path = ENV['PROCODILE_CONFIG'] || "/etc/procodile"
|
5
|
-
if File.exist?(global_config_path)
|
6
|
-
global_config = YAML.load_file(global_config_path)
|
7
|
-
else
|
8
|
-
global_config = {}
|
9
|
-
end
|
10
|
-
|
11
|
-
if global_config['user'] && ENV['USER'] != global_config['user']
|
12
|
-
if global_config['user_reexec']
|
13
|
-
$stderr.puts "\e[31mProcodile must be run as #{global_config['user']}. Re-executing as #{global_config['user']}...\e[0m"
|
14
|
-
exec "sudo -u #{global_config['user']} -- #{$0} #{ARGV.join(' ')}"
|
15
|
-
else
|
16
|
-
$stderr.puts "\e[31mError: Procodile must be run as #{global_config['user']}\e[0m"
|
17
|
-
exit 1
|
18
|
-
end
|
19
|
-
end
|
3
|
+
trap("INT") { puts ; exit 1 }
|
20
4
|
|
21
5
|
$:.unshift(File.expand_path('../../lib', __FILE__))
|
6
|
+
|
22
7
|
require 'optparse'
|
23
8
|
require 'fileutils'
|
9
|
+
require 'yaml'
|
24
10
|
require 'procodile'
|
25
11
|
require 'procodile/version'
|
26
12
|
require 'procodile/error'
|
@@ -46,7 +32,7 @@ begin
|
|
46
32
|
end
|
47
33
|
|
48
34
|
opts.on("--procfile PATH", "The path to the Procfile (defaults to: Procfile)") do |path|
|
49
|
-
options[:
|
35
|
+
options[:procfile] = path
|
50
36
|
end
|
51
37
|
|
52
38
|
if cli.class.commands[command.to_sym] && option_block = cli.class.commands[command.to_sym][:options]
|
@@ -58,11 +44,90 @@ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
|
58
44
|
exit 1
|
59
45
|
end
|
60
46
|
|
47
|
+
root = nil
|
48
|
+
procfile = nil
|
49
|
+
global_config = {}
|
50
|
+
if options[:root] && options[:procfile]
|
51
|
+
# root and profile provided on the command line
|
52
|
+
root = options[:root]
|
53
|
+
procfile = options[:procfile]
|
54
|
+
elsif options[:root] && options[:procfile].nil?
|
55
|
+
# root provided on the command line but no procfile
|
56
|
+
root = options[:root]
|
57
|
+
procfile = nil
|
58
|
+
elsif options[:root].nil? && options[:procfile]
|
59
|
+
# no root provided but a procfile is provided
|
60
|
+
procfile = File.expand_path(options[:procfile])
|
61
|
+
root = File.dirname(procfile)
|
62
|
+
else
|
63
|
+
# we don't really know what to do in this situation, if there's a global config
|
64
|
+
# file we can use that
|
65
|
+
global_config_path = ENV['PROCODILE_CONFIG'] || "/etc/procodile"
|
66
|
+
if File.exist?(global_config_path)
|
67
|
+
global_config = YAML.load_file(global_config_path)
|
68
|
+
if global_config.is_a?(Array)
|
69
|
+
puts "There are multiple applications configured in #{global_config_path}"
|
70
|
+
if File.file?("Procfile")
|
71
|
+
puts "\e[45;37mChoose an appplication or press ENTER to use the current directory:\e[0m"
|
72
|
+
else
|
73
|
+
puts "\e[45;37mChoose an application:\e[0m"
|
74
|
+
end
|
75
|
+
global_config.each_with_index do |app, i|
|
76
|
+
col = i % 3
|
77
|
+
print "#{(i+1)}) #{app['name']}"[0,28].ljust(col != 2 ? 30 : 0, ' ')
|
78
|
+
if col == 2 || i == global_config.size - 1
|
79
|
+
puts
|
80
|
+
end
|
81
|
+
end
|
82
|
+
app_id = STDIN.gets.to_i
|
83
|
+
if app_id == 0
|
84
|
+
if File.file?('Procfile')
|
85
|
+
puts "Skipping application selection... using current directory"
|
86
|
+
global_config = {}
|
87
|
+
else
|
88
|
+
exit 1
|
89
|
+
end
|
90
|
+
elsif app = global_config[app_id - 1]
|
91
|
+
puts "\e[35mYou selected #{app['name']}\e[0m"
|
92
|
+
global_config = app
|
93
|
+
else
|
94
|
+
puts "Invalid app number"
|
95
|
+
exit 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if global_config['user'] && ENV['USER'] != global_config['user']
|
102
|
+
if global_config['user_reexec']
|
103
|
+
$stderr.puts "\e[31mProcodile must be run as #{global_config['user']}. Re-executing as #{global_config['user']}...\e[0m"
|
104
|
+
exec "sudo -u #{global_config['user']} -- #{$0} #{ARGV.join(' ')}"
|
105
|
+
else
|
106
|
+
$stderr.puts "\e[31mError: Procodile must be run as #{global_config['user']}\e[0m"
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if global_config['root'] && global_config['procfile']
|
112
|
+
# the global config specifies a root and a procfile
|
113
|
+
root = global_config['root']
|
114
|
+
procfile = File.expand_path(global_config['procfile'], root)
|
115
|
+
elsif global_config['root'] && global_config['procfile'].nil?
|
116
|
+
root = global_config['root']
|
117
|
+
procfile = nil # assume from the root
|
118
|
+
elsif global_config['root'].nil? && global_config['procfile']
|
119
|
+
procfile = global_config['procfile']
|
120
|
+
root = File.dirname(procfile)
|
121
|
+
else
|
122
|
+
root ||= FileUtils.pwd
|
123
|
+
procfile ||= nil
|
124
|
+
end
|
125
|
+
|
61
126
|
begin
|
62
127
|
if command != 'help'
|
63
|
-
cli.config = Procodile::Config.new(
|
128
|
+
cli.config = Procodile::Config.new(root, options[:environment], procfile)
|
64
129
|
end
|
65
|
-
cli.
|
130
|
+
cli.dispatch(command)
|
66
131
|
rescue Procodile::Error => e
|
67
132
|
$stderr.puts "Error: #{e.message}".color(31)
|
68
133
|
exit 1
|
data/lib/procodile.rb
CHANGED
data/lib/procodile/cli.rb
CHANGED
@@ -34,7 +34,7 @@ module Procodile
|
|
34
34
|
@options = {}
|
35
35
|
end
|
36
36
|
|
37
|
-
def
|
37
|
+
def dispatch(command)
|
38
38
|
if self.class.commands.keys.include?(command.to_sym)
|
39
39
|
public_send(command)
|
40
40
|
else
|
@@ -54,7 +54,7 @@ module Procodile
|
|
54
54
|
|
55
55
|
puts "The following commands are supported:"
|
56
56
|
puts
|
57
|
-
self.class.commands.each do |method, options|
|
57
|
+
self.class.commands.sort_by { |k,v| k.to_s }.each do |method, options|
|
58
58
|
puts " \e[34m#{method.to_s.ljust(18, ' ')}\e[0m #{options[:description]}"
|
59
59
|
end
|
60
60
|
puts
|
@@ -104,6 +104,13 @@ module Procodile
|
|
104
104
|
cli.options[:proxy] = true
|
105
105
|
end
|
106
106
|
|
107
|
+
opts.on("--ports PROCESSES", "Choose ports to allocate to processes") do |processes|
|
108
|
+
cli.options[:port_allocations] = processes.split(/\,/).each_with_object({}) do |line, hash|
|
109
|
+
process, port = line.split(':')
|
110
|
+
hash[process] = port.to_i
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
107
114
|
opts.on("-d", "--dev", "Run in development mode") do
|
108
115
|
cli.options[:development] = true
|
109
116
|
cli.options[:respawn] = false
|
@@ -130,7 +137,7 @@ module Procodile
|
|
130
137
|
raise Error, "Cannot enable the proxy when the supervisor is running"
|
131
138
|
end
|
132
139
|
|
133
|
-
instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option, :tag => @options[:tag])
|
140
|
+
instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option, :tag => @options[:tag], :port_allocations => @options[:port_allocations])
|
134
141
|
if instances.empty?
|
135
142
|
puts "No processes to start."
|
136
143
|
else
|
@@ -145,7 +152,7 @@ module Procodile
|
|
145
152
|
if @options[:start_supervisor] == false
|
146
153
|
raise Error, "Supervisor is not running and cannot be started because --no-supervisor is set"
|
147
154
|
else
|
148
|
-
start_supervisor do |supervisor|
|
155
|
+
self.class.start_supervisor(@config, @options) do |supervisor|
|
149
156
|
unless @options[:start_processes] == false
|
150
157
|
supervisor.start_processes(process_names_from_cli_option, :tag => @options[:tag])
|
151
158
|
end
|
@@ -332,7 +339,6 @@ module Procodile
|
|
332
339
|
#
|
333
340
|
# Kill
|
334
341
|
#
|
335
|
-
|
336
342
|
desc "Forcefully kill all known processes"
|
337
343
|
command def kill
|
338
344
|
Dir[File.join(@config.pid_root, '*.pid')].each do |pid_path|
|
@@ -347,6 +353,69 @@ module Procodile
|
|
347
353
|
end
|
348
354
|
end
|
349
355
|
|
356
|
+
#
|
357
|
+
# Run a command with a procodile environment
|
358
|
+
#
|
359
|
+
desc "Run a command within the environment"
|
360
|
+
command def run
|
361
|
+
desired_command = ARGV.drop(1).join(' ')
|
362
|
+
exec(@config.environment_variables, desired_command)
|
363
|
+
end
|
364
|
+
|
365
|
+
#
|
366
|
+
# Run the configured console command
|
367
|
+
#
|
368
|
+
desc "Open a console within the environment"
|
369
|
+
command def console
|
370
|
+
if cmd = @config.console_command
|
371
|
+
environment = @config.environment_variables
|
372
|
+
exec(environment, cmd)
|
373
|
+
else
|
374
|
+
raise Error, "No console command has been configured in the Procfile"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
#
|
379
|
+
# Open up the procodile log if it exists
|
380
|
+
#
|
381
|
+
desc "Open a console within the environment"
|
382
|
+
options do |opts, cli|
|
383
|
+
opts.on("-f", "Wait for additional data and display it straight away") do
|
384
|
+
cli.options[:wait] = true
|
385
|
+
end
|
386
|
+
|
387
|
+
opts.on("-n LINES", "The number of previous lines to return") do |lines|
|
388
|
+
cli.options[:lines] = lines.to_i
|
389
|
+
end
|
390
|
+
|
391
|
+
opts.on("-p PROCESS", "--process PROCESS", "Show the log for a given process (rather than procodile)") do |process|
|
392
|
+
cli.options[:process] = process
|
393
|
+
end
|
394
|
+
|
395
|
+
end
|
396
|
+
command def log
|
397
|
+
opts = []
|
398
|
+
opts << "-f" if options[:wait]
|
399
|
+
opts << "-n #{options[:lines]}" if options[:lines]
|
400
|
+
|
401
|
+
if options[:process]
|
402
|
+
if process = @config.processes[options[:process]]
|
403
|
+
log_path = process.log_path
|
404
|
+
else
|
405
|
+
raise Error, "Invalid process name '#{options[:process]}'"
|
406
|
+
end
|
407
|
+
else
|
408
|
+
log_path = @config.log_path
|
409
|
+
end
|
410
|
+
puts opts
|
411
|
+
if File.exist?(log_path)
|
412
|
+
exec("tail #{opts.join(' ')} #{log_path}")
|
413
|
+
else
|
414
|
+
raise Error, "No file found at #{log_path}"
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
|
350
419
|
private
|
351
420
|
|
352
421
|
def supervisor_running?
|
@@ -360,18 +429,14 @@ module Procodile
|
|
360
429
|
end
|
361
430
|
|
362
431
|
def current_pid
|
363
|
-
if File.exist?(
|
364
|
-
pid_file = File.read(
|
432
|
+
if File.exist?(@config.supervisor_pid_path)
|
433
|
+
pid_file = File.read(@config.supervisor_pid_path).strip
|
365
434
|
pid_file.length > 0 ? pid_file.to_i : nil
|
366
435
|
else
|
367
436
|
nil
|
368
437
|
end
|
369
438
|
end
|
370
439
|
|
371
|
-
def pid_path
|
372
|
-
File.join(@config.pid_root, 'procodile.pid')
|
373
|
-
end
|
374
|
-
|
375
440
|
def process_names_from_cli_option
|
376
441
|
if @options[:processes]
|
377
442
|
processes = @options[:processes].split(',')
|
@@ -390,38 +455,38 @@ module Procodile
|
|
390
455
|
end
|
391
456
|
end
|
392
457
|
|
393
|
-
def start_supervisor(&after_start)
|
458
|
+
def self.start_supervisor(config, options = {}, &after_start)
|
394
459
|
run_options = {}
|
395
|
-
run_options[:respawn] =
|
396
|
-
run_options[:stop_when_none] =
|
397
|
-
run_options[:proxy] =
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
if
|
402
|
-
FileUtils.rm_rf(Dir[File.join(
|
460
|
+
run_options[:respawn] = options[:respawn]
|
461
|
+
run_options[:stop_when_none] = options[:stop_when_none]
|
462
|
+
run_options[:proxy] = options[:proxy]
|
463
|
+
run_options[:force_single_log] = options[:foreground]
|
464
|
+
run_options[:port_allocations] = options[:port_allocations]
|
465
|
+
|
466
|
+
if options[:clean]
|
467
|
+
FileUtils.rm_rf(Dir[File.join(config.pid_root, '*')])
|
403
468
|
puts "Emptied PID directory"
|
404
469
|
end
|
405
470
|
|
406
|
-
if !Dir[File.join(
|
407
|
-
raise Error, "The PID directory (#{
|
471
|
+
if !Dir[File.join(config.pid_root, "*")].empty?
|
472
|
+
raise Error, "The PID directory (#{config.pid_root}) is not empty. Cannot start unless things are clean."
|
408
473
|
end
|
409
474
|
|
410
|
-
$0="[procodile] #{
|
411
|
-
if
|
412
|
-
File.open(
|
413
|
-
Supervisor.new(
|
475
|
+
$0="[procodile] #{config.app_name} (#{config.root})"
|
476
|
+
if options[:foreground]
|
477
|
+
File.open(config.supervisor_pid_path, 'w') { |f| f.write(::Process.pid) }
|
478
|
+
Supervisor.new(config, run_options).start(&after_start)
|
414
479
|
else
|
415
|
-
FileUtils.rm_f(File.join(
|
480
|
+
FileUtils.rm_f(File.join(config.pid_root, "*.pid"))
|
416
481
|
pid = fork do
|
417
|
-
STDOUT.reopen(
|
482
|
+
STDOUT.reopen(config.log_path, 'a')
|
418
483
|
STDOUT.sync = true
|
419
|
-
STDERR.reopen(
|
484
|
+
STDERR.reopen(config.log_path, 'a')
|
420
485
|
STDERR.sync = true
|
421
|
-
Supervisor.new(
|
486
|
+
Supervisor.new(config, run_options).start(&after_start)
|
422
487
|
end
|
423
488
|
::Process.detach(pid)
|
424
|
-
File.open(
|
489
|
+
File.open(config.supervisor_pid_path, 'w') { |f| f.write(pid) }
|
425
490
|
puts "Started Procodile supervisor with PID #{pid}"
|
426
491
|
end
|
427
492
|
end
|
data/lib/procodile/config.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'fileutils'
|
2
3
|
require 'procodile/error'
|
3
4
|
require 'procodile/process'
|
4
5
|
|
@@ -10,11 +11,11 @@ module Procodile
|
|
10
11
|
attr_reader :root
|
11
12
|
attr_reader :environment
|
12
13
|
|
13
|
-
def initialize(root, environment, procfile = nil)
|
14
|
+
def initialize(root, environment = nil, procfile = nil)
|
14
15
|
@root = root
|
15
16
|
@environment = environment || 'production'
|
16
17
|
@procfile_path = procfile
|
17
|
-
unless File.
|
18
|
+
unless File.file?(procfile_path)
|
18
19
|
raise Error, "Procfile not found at #{procfile_path}"
|
19
20
|
end
|
20
21
|
FileUtils.mkdir_p(pid_root)
|
@@ -62,16 +63,20 @@ module Procodile
|
|
62
63
|
@app_name ||= fetch(local_options['app_name']) || fetch(options['app_name']) || 'Procodile'
|
63
64
|
end
|
64
65
|
|
66
|
+
def console_command
|
67
|
+
fetch(local_options['console_command']) || fetch(options['console_command'])
|
68
|
+
end
|
69
|
+
|
65
70
|
def processes
|
66
71
|
@processes ||= {}
|
67
72
|
end
|
68
73
|
|
69
74
|
def process_list
|
70
|
-
@process_list ||=
|
75
|
+
@process_list ||= load_process_list_from_file
|
71
76
|
end
|
72
77
|
|
73
78
|
def options
|
74
|
-
@options ||=
|
79
|
+
@options ||= load_options_from_file
|
75
80
|
end
|
76
81
|
|
77
82
|
def process_options
|
@@ -79,7 +84,7 @@ module Procodile
|
|
79
84
|
end
|
80
85
|
|
81
86
|
def local_options
|
82
|
-
@local_options ||=
|
87
|
+
@local_options ||= load_local_options_from_file
|
83
88
|
end
|
84
89
|
|
85
90
|
def local_process_options
|
@@ -98,6 +103,10 @@ module Procodile
|
|
98
103
|
@pid_root ||= File.expand_path(fetch(local_options['pid_root']) || fetch(options['pid_root']) || 'pids', @root)
|
99
104
|
end
|
100
105
|
|
106
|
+
def supervisor_pid_path
|
107
|
+
File.join(pid_root, 'procodile.pid')
|
108
|
+
end
|
109
|
+
|
101
110
|
def log_path
|
102
111
|
log_path = fetch(local_options['log_path']) || fetch(options['log_path'])
|
103
112
|
if log_path
|
@@ -121,8 +130,6 @@ module Procodile
|
|
121
130
|
File.join(pid_root, 'procodile.sock')
|
122
131
|
end
|
123
132
|
|
124
|
-
private
|
125
|
-
|
126
133
|
def procfile_path
|
127
134
|
@procfile_path || File.join(@root, 'Procfile')
|
128
135
|
end
|
@@ -135,6 +142,8 @@ module Procodile
|
|
135
142
|
procfile_path + ".local"
|
136
143
|
end
|
137
144
|
|
145
|
+
private
|
146
|
+
|
138
147
|
def create_process(name, command, log_color)
|
139
148
|
process = Process.new(self, name, command, options_for_process(name))
|
140
149
|
process.log_color = log_color
|
@@ -161,5 +170,17 @@ module Procodile
|
|
161
170
|
end
|
162
171
|
end
|
163
172
|
|
173
|
+
def load_process_list_from_file
|
174
|
+
YAML.load_file(procfile_path)
|
175
|
+
end
|
176
|
+
|
177
|
+
def load_options_from_file
|
178
|
+
File.exist?(options_path) ? YAML.load_file(options_path) : {}
|
179
|
+
end
|
180
|
+
|
181
|
+
def load_local_options_from_file
|
182
|
+
File.exist?(local_options_path) ? YAML.load_file(local_options_path) : {}
|
183
|
+
end
|
184
|
+
|
164
185
|
end
|
165
186
|
end
|
@@ -25,6 +25,13 @@ module Procodile
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def start_processes(options)
|
28
|
+
if options['port_allocations']
|
29
|
+
if @supervisor.run_options[:port_allocations]
|
30
|
+
@supervisor.run_options[:port_allocations].merge!(options['port_allocations'])
|
31
|
+
else
|
32
|
+
@supervisor.run_options[:port_allocations] = options['port_allocations']
|
33
|
+
end
|
34
|
+
end
|
28
35
|
instances = @supervisor.start_processes(options['processes'], :tag => options['tag'])
|
29
36
|
"200 " + instances.map(&:to_hash).to_json
|
30
37
|
end
|
data/lib/procodile/instance.rb
CHANGED
@@ -100,12 +100,23 @@ module Procodile
|
|
100
100
|
Procodile.log(@process.log_color, description, "Already running with PID #{@pid}")
|
101
101
|
nil
|
102
102
|
else
|
103
|
+
if @supervisor.run_options[:port_allocations] && chosen_port = @supervisor.run_options[:port_allocations][@process.name]
|
104
|
+
if chosen_port == 0
|
105
|
+
allocate_port
|
106
|
+
else
|
107
|
+
@port = chosen_port
|
108
|
+
Procodile.log(@process.log_color, description, "Assigned #{chosen_port} to process")
|
109
|
+
end
|
110
|
+
elsif @process.proxy? && @supervisor.tcp_proxy
|
111
|
+
# Allocate a port randomly if a proxy is needed
|
112
|
+
allocate_port
|
113
|
+
end
|
103
114
|
|
104
|
-
if @process.proxy? && @supervisor.tcp_proxy
|
115
|
+
if (@supervisor.run_options[:allocate_ports] && @supervisor.run_options[:allocate_ports].include?(@process.name)) || (@process.proxy? && @supervisor.tcp_proxy)
|
105
116
|
allocate_port
|
106
117
|
end
|
107
118
|
|
108
|
-
if self.process.log_path
|
119
|
+
if self.process.log_path && @supervisor.run_options[:force_single_log] != true
|
109
120
|
log_destination = File.open(self.process.log_path, 'a')
|
110
121
|
io = nil
|
111
122
|
else
|
@@ -115,13 +126,15 @@ module Procodile
|
|
115
126
|
end
|
116
127
|
@tag = @supervisor.tag.dup if @supervisor.tag
|
117
128
|
Dir.chdir(@process.config.root)
|
118
|
-
|
129
|
+
without_rbenv do
|
130
|
+
@pid = ::Process.spawn(environment_variables, @process.command, :out => log_destination, :err => log_destination, :pgroup => true)
|
131
|
+
end
|
119
132
|
log_destination.close
|
120
133
|
File.open(pid_file_path, 'w') { |f| f.write(@pid.to_s + "\n") }
|
121
134
|
@supervisor.add_instance(self, io)
|
122
135
|
::Process.detach(@pid)
|
123
136
|
Procodile.log(@process.log_color, description, "Started with PID #{@pid}" + (@tag ? " (tagged with #{@tag})" : ''))
|
124
|
-
if self.process.log_path
|
137
|
+
if self.process.log_path && io.nil?
|
125
138
|
Procodile.log(@process.log_color, description, "Logging to #{self.process.log_path}")
|
126
139
|
end
|
127
140
|
@started_at = Time.now
|
@@ -315,18 +328,59 @@ module Procodile
|
|
315
328
|
# Find a port number for this instance to listen on. We just check that nothing is already listening on it.
|
316
329
|
# The process is expected to take it straight away if it wants it.
|
317
330
|
#
|
318
|
-
def allocate_port
|
331
|
+
def allocate_port(max_attempts = 10)
|
332
|
+
attempts = 0
|
319
333
|
until @port
|
334
|
+
attempts += 1
|
320
335
|
possible_port = rand(10000) + 20000
|
321
|
-
|
322
|
-
|
323
|
-
server.close
|
336
|
+
if self.port_available?(possible_port)
|
337
|
+
Procodile.log(@process.log_color, description, "Allocated port as #{possible_port}")
|
324
338
|
return @port = possible_port
|
325
|
-
|
326
|
-
#
|
339
|
+
elsif attempts >= max_attempts
|
340
|
+
raise Procodile::Error, "Couldn't allocate port for #{instance.name}"
|
327
341
|
end
|
328
342
|
end
|
329
343
|
end
|
330
344
|
|
345
|
+
#
|
346
|
+
# Is the given port available?
|
347
|
+
#
|
348
|
+
def port_available?(port)
|
349
|
+
case @process.network_protocol
|
350
|
+
when 'tcp'
|
351
|
+
server = TCPServer.new('127.0.0.1', port)
|
352
|
+
server.close
|
353
|
+
true
|
354
|
+
when 'udp'
|
355
|
+
server = UDPSocket.new
|
356
|
+
server.bind('127.0.0.1', port)
|
357
|
+
server.close
|
358
|
+
true
|
359
|
+
else
|
360
|
+
raise Procodile::Error, "Invalid network_protocol '#{@process.network_protocol}'"
|
361
|
+
end
|
362
|
+
rescue Errno::EADDRINUSE => e
|
363
|
+
false
|
364
|
+
end
|
365
|
+
|
366
|
+
#
|
367
|
+
# If procodile is executed through rbenv it will pollute our environment which means that
|
368
|
+
# any spawned processes will be invoked with procodile's ruby rather than the ruby that
|
369
|
+
# the application wishes to use
|
370
|
+
#
|
371
|
+
def without_rbenv(&block)
|
372
|
+
previous_environment = ENV.select { |k,v| k =~ /\A(RBENV\_)/ }
|
373
|
+
if previous_environment.size > 0
|
374
|
+
previous_environment.each { |key, value| ENV[key] = nil }
|
375
|
+
previous_environment['PATH'] = ENV['PATH']
|
376
|
+
ENV['PATH'] = ENV['PATH'].split(':').select { |p| !(p =~ /\.rbenv\/versions/) }.join(':')
|
377
|
+
end
|
378
|
+
yield
|
379
|
+
ensure
|
380
|
+
previous_environment.each do |key, value|
|
381
|
+
ENV[key] = value
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
331
385
|
end
|
332
386
|
end
|
data/lib/procodile/process.rb
CHANGED
@@ -12,11 +12,10 @@ module Procodile
|
|
12
12
|
attr_accessor :log_color
|
13
13
|
attr_accessor :removed
|
14
14
|
|
15
|
-
def initialize(config, name, command,
|
15
|
+
def initialize(config, name, command, options = {})
|
16
16
|
@config = config
|
17
17
|
@name = name
|
18
18
|
@command = command
|
19
|
-
@environment = environment
|
20
19
|
@options = options
|
21
20
|
@log_color = 0
|
22
21
|
@instance_index = 0
|
@@ -132,6 +131,12 @@ module Procodile
|
|
132
131
|
proxy? ? @options['proxy_address'] || '127.0.0.1' : nil
|
133
132
|
end
|
134
133
|
|
134
|
+
#
|
135
|
+
# Return the network protocol for this process
|
136
|
+
#
|
137
|
+
def network_protocol
|
138
|
+
@options['network_protocol'] || 'tcp'
|
139
|
+
end
|
135
140
|
|
136
141
|
#
|
137
142
|
# Generate an array of new instances for this process (based on its quantity)
|
@@ -166,5 +171,16 @@ module Procodile
|
|
166
171
|
}
|
167
172
|
end
|
168
173
|
|
174
|
+
#
|
175
|
+
# Is the given quantity suitable for this process?
|
176
|
+
#
|
177
|
+
def correct_quantity?(quantity)
|
178
|
+
if self.restart_mode == 'start-term'
|
179
|
+
quantity >= self.quantity
|
180
|
+
else
|
181
|
+
self.quantity == quantity
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
169
185
|
end
|
170
186
|
end
|
@@ -59,10 +59,10 @@ module Procodile
|
|
59
59
|
print "|| => ".color(process['log_color']) + instance['description'].to_s.ljust(17, ' ').color(process['log_color'])
|
60
60
|
print instance['status'].ljust(10, ' ')
|
61
61
|
print " " + formatted_timestamp(instance['started_at']).ljust(10, ' ')
|
62
|
-
print " " + instance['pid'].to_s.ljust(6, ' ')
|
63
|
-
print " " + instance['respawns'].to_s.ljust(4, ' ')
|
64
|
-
print " " + (instance['port'] || "-").to_s.ljust(6, ' ')
|
65
|
-
print " " + (instance['tag'] || "-").to_s
|
62
|
+
print " pid:" + instance['pid'].to_s.ljust(6, ' ')
|
63
|
+
print " respawns:" + instance['respawns'].to_s.ljust(4, ' ')
|
64
|
+
print " port:" + (instance['port'] || "-").to_s.ljust(6, ' ')
|
65
|
+
print " tag:" + (instance['tag'] || "-").to_s
|
66
66
|
puts
|
67
67
|
end
|
68
68
|
end
|
data/lib/procodile/supervisor.rb
CHANGED
@@ -9,6 +9,7 @@ module Procodile
|
|
9
9
|
attr_reader :started_at
|
10
10
|
attr_reader :tag
|
11
11
|
attr_reader :tcp_proxy
|
12
|
+
attr_reader :run_options
|
12
13
|
|
13
14
|
def initialize(config, run_options = {})
|
14
15
|
@config = config
|
@@ -19,7 +20,7 @@ module Procodile
|
|
19
20
|
@signal_handler.register('TERM') { stop_supervisor }
|
20
21
|
@signal_handler.register('INT') { stop(:stop_supervisor => true) }
|
21
22
|
@signal_handler.register('USR1') { restart }
|
22
|
-
@signal_handler.register('USR2') {
|
23
|
+
@signal_handler.register('USR2') { }
|
23
24
|
@signal_handler.register('HUP') { reload_config }
|
24
25
|
end
|
25
26
|
|
@@ -41,6 +42,15 @@ module Procodile
|
|
41
42
|
watch_for_output
|
42
43
|
@started_at = Time.now
|
43
44
|
after_start.call(self) if block_given?
|
45
|
+
supervise!
|
46
|
+
rescue => e
|
47
|
+
Procodile.log nil, "system", "Error: #{e.class} (#{e.message})"
|
48
|
+
e.backtrace.each { |bt| Procodile.log nil, "system", "=> #{bt})" }
|
49
|
+
stop(:stop_supervisor => true)
|
50
|
+
supervise!
|
51
|
+
end
|
52
|
+
|
53
|
+
def supervise!
|
44
54
|
loop { supervise; sleep 3 }
|
45
55
|
end
|
46
56
|
|
@@ -130,7 +140,7 @@ module Procodile
|
|
130
140
|
|
131
141
|
if @run_options[:stop_when_none]
|
132
142
|
# If the processes go away, we can stop the supervisor now
|
133
|
-
if @processes.all? { |_,instances| instances.size == 0 }
|
143
|
+
if @processes.all? { |_,instances| instances.reject(&:failed?).size == 0 }
|
134
144
|
Procodile.log nil, "system", "All processes have stopped"
|
135
145
|
stop_supervisor
|
136
146
|
end
|
@@ -170,7 +180,7 @@ module Procodile
|
|
170
180
|
def messages
|
171
181
|
messages = []
|
172
182
|
processes.each do |process, process_instances|
|
173
|
-
|
183
|
+
unless process.correct_quantity?(process_instances.size)
|
174
184
|
messages << {:type => :incorrect_quantity, :process => process.name, :current => process_instances.size, :desired => process.quantity}
|
175
185
|
end
|
176
186
|
for instance in process_instances
|
data/lib/procodile/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: procodile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|