procodile 1.0.10 → 1.0.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc9014711e488092ae55249f9fcda8dbb2fb9cc1
4
- data.tar.gz: 9181ab265781b1b39a20524705eab6b6c31117e9
3
+ metadata.gz: 90d57673d57a817411b10a60ebbaa04b8222f644
4
+ data.tar.gz: a928bbe14abe7ad22dee7e98763ef42d12abffb3
5
5
  SHA512:
6
- metadata.gz: 4b12e1eb9fb33548ea5b01d757a73cf377a024cc6e5a0877168fe1240e359c29f569a9b7fcc888c1d8d20008dcdad54497292b79471db1d5efc44b848b2f48a6
7
- data.tar.gz: 135e12b6084a9cddad8736913bec02ab0dd591728f212340e60e7f83302887dc80551adc09b19cf220306ebde3e742f8b6fc6669cc7794228f84d1f0a39546c7
6
+ metadata.gz: 227c83421e688d37e8fc544959ead123ca7c1abb5e0f00c465525a7298225a4b3af000eba4b1041ee57ffbba1cb91a7e7332457e35def291697b01dd510436a0
7
+ data.tar.gz: ce6efd84d43fc76f094ef4f8788c7848d3992548f12493ea529aa0321ec0819c5e9d752a02451227ac57833542e435173aa574136f33beb2a40743cc2b12af55
@@ -1,26 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'yaml'
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[:procfile_path] = path
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(options[:root] ? File.expand_path(options[:root]) : global_config['root'] || FileUtils.pwd, options[:environment], options[:procfile_path])
128
+ cli.config = Procodile::Config.new(root, options[:environment], procfile)
64
129
  end
65
- cli.run(command)
130
+ cli.dispatch(command)
66
131
  rescue Procodile::Error => e
67
132
  $stderr.puts "Error: #{e.message}".color(31)
68
133
  exit 1
@@ -1,4 +1,13 @@
1
1
  module Procodile
2
+
3
+ def self.root
4
+ File.expand_path('../../', __FILE__)
5
+ end
6
+
7
+ def self.bin_path
8
+ File.join(root, 'bin', 'procodile')
9
+ end
10
+
2
11
  end
3
12
 
4
13
  class String
@@ -34,7 +34,7 @@ module Procodile
34
34
  @options = {}
35
35
  end
36
36
 
37
- def run(command)
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?(pid_path)
364
- pid_file = File.read(pid_path).strip
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] = @options[:respawn]
396
- run_options[:stop_when_none] = @options[:stop_when_none]
397
- run_options[:proxy] = @options[:proxy]
398
-
399
- processes = process_names_from_cli_option
400
-
401
- if @options[:clean]
402
- FileUtils.rm_rf(Dir[File.join(@config.pid_root, '*')])
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(@config.pid_root, "*")].empty?
407
- raise Error, "The PID directory (#{@config.pid_root}) is not empty. Cannot start unless things are clean."
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] #{@config.app_name} (#{@config.root})"
411
- if @options[:foreground]
412
- File.open(pid_path, 'w') { |f| f.write(::Process.pid) }
413
- Supervisor.new(@config, run_options).start(&after_start)
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(@config.pid_root, "*.pid"))
480
+ FileUtils.rm_f(File.join(config.pid_root, "*.pid"))
416
481
  pid = fork do
417
- STDOUT.reopen(@config.log_path, 'a')
482
+ STDOUT.reopen(config.log_path, 'a')
418
483
  STDOUT.sync = true
419
- STDERR.reopen(@config.log_path, 'a')
484
+ STDERR.reopen(config.log_path, 'a')
420
485
  STDERR.sync = true
421
- Supervisor.new(@config, run_options).start(&after_start)
486
+ Supervisor.new(config, run_options).start(&after_start)
422
487
  end
423
488
  ::Process.detach(pid)
424
- File.open(pid_path, 'w') { |f| f.write(pid) }
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
@@ -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.exist?(procfile_path)
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 ||= YAML.load_file(procfile_path) || {}
75
+ @process_list ||= load_process_list_from_file
71
76
  end
72
77
 
73
78
  def options
74
- @options ||= File.exist?(options_path) ? YAML.load_file(options_path) : {}
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 ||= File.exist?(local_options_path) ? YAML.load_file(local_options_path) : {}
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
@@ -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
- @pid = ::Process.spawn(environment_variables, @process.command, :out => log_destination, :err => log_destination, :pgroup => true)
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
- begin
322
- server = TCPServer.new('127.0.0.1', possible_port)
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
- rescue
326
- # Nah.
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
@@ -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, environment, options = {})
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
@@ -36,6 +36,7 @@ module Procodile
36
36
 
37
37
  def handle
38
38
  if signal = self.class.queue.shift
39
+ Procodile.log nil, 'system', "Supervisor received #{signal} signal"
39
40
  if @handlers[signal]
40
41
  @handlers[signal].each(&:call)
41
42
  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
@@ -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') { status }
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
- if process.quantity != process_instances.size
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
@@ -1,3 +1,3 @@
1
1
  module Procodile
2
- VERSION = '1.0.10'
2
+ VERSION = '1.0.11'
3
3
  end
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.10
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-02-28 00:00:00.000000000 Z
11
+ date: 2017-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json