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 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