procodile 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/procodile +17 -35
- data/lib/procodile/cli.rb +176 -42
- data/lib/procodile/config.rb +48 -22
- data/lib/procodile/control_session.rb +19 -9
- data/lib/procodile/instance.rb +26 -7
- data/lib/procodile/status_cli_output.rb +77 -0
- data/lib/procodile/supervisor.rb +76 -30
- data/lib/procodile/version.rb +3 -0
- metadata +4 -4
- data/lib/procodile/capistrano2.rb +0 -39
- data/lib/procodile/capistrano3.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 812b0f566074632d4c15caddfd02e6b1ddf8ee88
|
4
|
+
data.tar.gz: 2acf4f380545089801e036b22aebbd1937d5be6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38ee04794ce4b93d537f20704cae020dd3f03e5af610f181a3a2c11a1657e5fc48dc486ad759d74cd60187e8859869176dd8d19df1f3ef6bcd7d01ca2e8bd11e
|
7
|
+
data.tar.gz: 57ff3aef3f9e8dcbbe3c0871f68e15e82b1fa18c10e035d891042774340388bf2de595bc1de95e6343e3b5f743e9ebd13a126c9db105efa925ddaaa8c034e1ba
|
data/bin/procodile
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
3
|
+
|
2
4
|
require 'optparse'
|
3
5
|
require 'fileutils'
|
4
|
-
$:.unshift(File.expand_path('../../lib', __FILE__))
|
5
6
|
require 'procodile'
|
7
|
+
require 'procodile/version'
|
8
|
+
require 'procodile/error'
|
9
|
+
require 'procodile/config'
|
10
|
+
require 'procodile/cli'
|
11
|
+
|
12
|
+
Thread.abort_on_exception = true
|
6
13
|
|
7
|
-
command = ARGV[0]
|
14
|
+
command = ARGV[0] || 'help'
|
15
|
+
cli = Procodile::CLI.new
|
8
16
|
|
9
17
|
options = {}
|
10
18
|
begin
|
11
19
|
OptionParser.new do |opts|
|
12
|
-
opts.
|
20
|
+
opts.version = Procodile::VERSION
|
21
|
+
opts.banner = "Usage: procodile #{command} [options]"
|
13
22
|
opts.on("-r", "--root PATH", "The path to the root of your application") do |root|
|
14
23
|
options[:root] = root
|
15
24
|
end
|
16
|
-
|
17
|
-
|
18
|
-
opts.on("-p", "--processes a,b,c", "Only #{command} the listed processes or process types") do |processes|
|
19
|
-
options[:processes] = processes
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
if command == 'start'
|
24
|
-
opts.on("-f", "--foreground", "Run the supervisor in the foreground") do
|
25
|
-
options[:foreground] = true
|
26
|
-
end
|
27
|
-
|
28
|
-
opts.on("--clean", "Remove all previous pid and sock files before starting") do
|
29
|
-
options[:clean] = true
|
30
|
-
end
|
31
|
-
|
32
|
-
opts.on("-b", "--brittle", "Kill everything when one process exits") do
|
33
|
-
options[:brittle] = true
|
34
|
-
end
|
35
|
-
|
36
|
-
opts.on("-d", "--dev", "Run in development mode") do
|
37
|
-
options[:development] = true
|
38
|
-
options[:brittle] = true
|
39
|
-
options[:foreground] = true
|
40
|
-
end
|
25
|
+
if cli.class.commands[command.to_sym] && option_block = cli.class.commands[command.to_sym][:options]
|
26
|
+
option_block.call(opts, cli)
|
41
27
|
end
|
42
28
|
end.parse!
|
43
29
|
rescue OptionParser::InvalidOption => e
|
@@ -45,14 +31,10 @@ rescue OptionParser::InvalidOption => e
|
|
45
31
|
exit 1
|
46
32
|
end
|
47
33
|
|
48
|
-
require 'procodile/error'
|
49
|
-
require 'procodile/config'
|
50
|
-
require 'procodile/cli'
|
51
|
-
|
52
|
-
Thread.abort_on_exception = true
|
53
34
|
begin
|
54
|
-
|
55
|
-
|
35
|
+
if command != 'help'
|
36
|
+
cli.config = Procodile::Config.new(options[:root] ? File.expand_path(options[:root]) : FileUtils.pwd)
|
37
|
+
end
|
56
38
|
cli.run(command)
|
57
39
|
rescue Procodile::Error => e
|
58
40
|
$stderr.puts "Error: #{e.message}".color(31)
|
data/lib/procodile/cli.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'procodile/version'
|
2
3
|
require 'procodile/error'
|
3
4
|
require 'procodile/supervisor'
|
4
5
|
require 'procodile/signal_handler'
|
@@ -7,20 +8,93 @@ require 'procodile/control_client'
|
|
7
8
|
module Procodile
|
8
9
|
class CLI
|
9
10
|
|
10
|
-
def
|
11
|
-
@
|
12
|
-
|
11
|
+
def self.commands
|
12
|
+
@commands ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.desc(description)
|
16
|
+
@description = description
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.options(&block)
|
20
|
+
@options = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.command(name)
|
24
|
+
commands[name] = {:name => name, :description => @description, :options => @options}
|
25
|
+
@description = nil
|
26
|
+
@options = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_accessor :options
|
30
|
+
attr_accessor :config
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@options = {}
|
13
34
|
end
|
14
35
|
|
15
36
|
def run(command)
|
16
|
-
if self.class.
|
37
|
+
if self.class.commands.keys.include?(command.to_sym)
|
17
38
|
public_send(command)
|
18
39
|
else
|
19
40
|
raise Error, "Invalid command '#{command}'"
|
20
41
|
end
|
21
42
|
end
|
22
43
|
|
23
|
-
|
44
|
+
#
|
45
|
+
# Help
|
46
|
+
#
|
47
|
+
|
48
|
+
desc "Shows this help output"
|
49
|
+
command def help
|
50
|
+
puts "\e[45;37mWelcome to Procodile v#{Procodile::VERSION}\e[0m"
|
51
|
+
puts "For documentation see https://adam.ac/procodile."
|
52
|
+
puts
|
53
|
+
|
54
|
+
puts "The following commands are supported:"
|
55
|
+
puts
|
56
|
+
self.class.commands.each do |method, options|
|
57
|
+
puts " \e[34m#{method.to_s.ljust(18, ' ')}\e[0m #{options[:description]}"
|
58
|
+
end
|
59
|
+
puts
|
60
|
+
puts "For details for the options available for each command, use the --help option."
|
61
|
+
puts "For example 'procodile start --help'."
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Start
|
66
|
+
#
|
67
|
+
|
68
|
+
desc "Starts processes and/or the supervisor"
|
69
|
+
options do |opts, cli|
|
70
|
+
opts.on("-p", "--processes a,b,c", "Only start the listed processes or process types") do |processes|
|
71
|
+
cli.options[:processes] = processes
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on("-f", "--foreground", "Run the supervisor in the foreground") do
|
75
|
+
cli.options[:foreground] = true
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on("--clean", "Remove all previous pid and sock files before starting") do
|
79
|
+
cli.options[:clean] = true
|
80
|
+
end
|
81
|
+
|
82
|
+
opts.on("-b", "--brittle", "Kill everything when one process exits") do
|
83
|
+
cli.options[:brittle] = true
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on("--stop-when-none", "Stop the supervisor when all processes are stopped") do
|
87
|
+
cli.options[:stop_when_none] = true
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on("-d", "--dev", "Run in development mode") do
|
91
|
+
cli.options[:development] = true
|
92
|
+
cli.options[:brittle] = true
|
93
|
+
cli.options[:foreground] = true
|
94
|
+
cli.options[:stop_when_none] = true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
command def start
|
24
98
|
if running?
|
25
99
|
instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option)
|
26
100
|
if instances.empty?
|
@@ -34,17 +108,18 @@ module Procodile
|
|
34
108
|
end
|
35
109
|
|
36
110
|
run_options = {}
|
37
|
-
run_options[:brittle] = @
|
111
|
+
run_options[:brittle] = @options[:brittle]
|
112
|
+
run_options[:stop_when_none] = @options[:stop_when_none]
|
38
113
|
|
39
114
|
processes = process_names_from_cli_option
|
40
115
|
|
41
|
-
if @
|
116
|
+
if @options[:clean]
|
42
117
|
FileUtils.rm_f(File.join(@config.pid_root, '*.pid'))
|
43
118
|
FileUtils.rm_f(File.join(@config.pid_root, '*.sock'))
|
44
119
|
puts "Removed all old pid & sock files"
|
45
120
|
end
|
46
121
|
|
47
|
-
if @
|
122
|
+
if @options[:foreground]
|
48
123
|
File.open(pid_path, 'w') { |f| f.write(::Process.pid) }
|
49
124
|
Supervisor.new(@config, run_options).start(:processes => processes)
|
50
125
|
else
|
@@ -62,10 +137,24 @@ module Procodile
|
|
62
137
|
end
|
63
138
|
end
|
64
139
|
|
65
|
-
|
140
|
+
#
|
141
|
+
# Stop
|
142
|
+
#
|
143
|
+
|
144
|
+
desc "Stops processes and/or the supervisor"
|
145
|
+
options do |opts, cli|
|
146
|
+
opts.on("-p", "--processes a,b,c", "Only stop the listed processes or process types") do |processes|
|
147
|
+
cli.options[:processes] = processes
|
148
|
+
end
|
149
|
+
|
150
|
+
opts.on("-s", "--stop-supervisor", "Stop the ") do
|
151
|
+
cli.options[:stop_supervisor] = true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
command def stop
|
66
155
|
if running?
|
67
156
|
options = {}
|
68
|
-
instances = ControlClient.run(@config.sock_path, 'stop', :processes => process_names_from_cli_option)
|
157
|
+
instances = ControlClient.run(@config.sock_path, 'stop', :processes => process_names_from_cli_option, :stop_supervisor => @options[:stop_supervisor])
|
69
158
|
if instances.empty?
|
70
159
|
puts "There are no processes to stop."
|
71
160
|
else
|
@@ -78,7 +167,17 @@ module Procodile
|
|
78
167
|
end
|
79
168
|
end
|
80
169
|
|
81
|
-
|
170
|
+
#
|
171
|
+
# Restart
|
172
|
+
#
|
173
|
+
|
174
|
+
desc "Restart processes"
|
175
|
+
options do |opts, cli|
|
176
|
+
opts.on("-p", "--processes a,b,c", "Only restart the listed processes or process types") do |processes|
|
177
|
+
cli.options[:processes] = processes
|
178
|
+
end
|
179
|
+
end
|
180
|
+
command def restart
|
82
181
|
if running?
|
83
182
|
options = {}
|
84
183
|
instances = ControlClient.run(@config.sock_path, 'restart', :processes => process_names_from_cli_option)
|
@@ -94,7 +193,12 @@ module Procodile
|
|
94
193
|
end
|
95
194
|
end
|
96
195
|
|
97
|
-
|
196
|
+
#
|
197
|
+
# Stop Supervisor
|
198
|
+
#
|
199
|
+
|
200
|
+
desc "Stop the supervisor without stopping processes"
|
201
|
+
command def stop_supervisor
|
98
202
|
if running?
|
99
203
|
::Process.kill('TERM', current_pid)
|
100
204
|
puts "Supervisor will be stopped in a moment."
|
@@ -103,49 +207,79 @@ module Procodile
|
|
103
207
|
end
|
104
208
|
end
|
105
209
|
|
106
|
-
|
210
|
+
#
|
211
|
+
# Reload Config
|
212
|
+
#
|
213
|
+
|
214
|
+
desc "Reload Procodile configuration"
|
215
|
+
command def reload
|
107
216
|
if running?
|
108
217
|
ControlClient.run(@config.sock_path, 'reload_config')
|
109
|
-
puts "
|
218
|
+
puts "Reloaded config for #{@config.app_name}"
|
110
219
|
else
|
111
220
|
raise Error, "#{@config.app_name} supervisor isn't running"
|
112
221
|
end
|
113
222
|
end
|
114
223
|
|
115
|
-
|
224
|
+
#
|
225
|
+
# Check process concurrency
|
226
|
+
#
|
227
|
+
|
228
|
+
desc "Check process concurrency"
|
229
|
+
options do |opts, cli|
|
230
|
+
opts.on("--no-reload", "Do not reload the configuration before checking") do |processes|
|
231
|
+
cli.options[:reload] = false
|
232
|
+
end
|
233
|
+
end
|
234
|
+
command def check_concurrency
|
116
235
|
if running?
|
117
|
-
|
118
|
-
|
119
|
-
puts
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
if instances.empty?
|
128
|
-
puts "||".color(process['log_color']) + " No processes running."
|
129
|
-
else
|
130
|
-
instances.each do |instance|
|
131
|
-
print "|| ".color(process['log_color']) + instance['description'].to_s.ljust(20, ' ').color(process['log_color'])
|
132
|
-
if instance['running']
|
133
|
-
print ' Running '.color("42;37")
|
134
|
-
else
|
135
|
-
print ' Stopped '.color("41;37")
|
136
|
-
end
|
137
|
-
print " pid " + instance['pid'].to_s.ljust(12, ' ')
|
138
|
-
print instance['respawns'].to_s + " respawns"
|
139
|
-
puts
|
140
|
-
end
|
236
|
+
reply = ControlClient.run(@config.sock_path, 'check_concurrency', :reload => @options[:reload])
|
237
|
+
if reply['started'].empty? && reply['stopped'].empty?
|
238
|
+
puts "Everything looks good!"
|
239
|
+
else
|
240
|
+
reply['started'].each do |instance|
|
241
|
+
puts "Started #{instance['description']}".color(32)
|
242
|
+
end
|
243
|
+
|
244
|
+
reply['stopped'].each do |instance|
|
245
|
+
puts "Stopped #{instance['description']}".color(31)
|
141
246
|
end
|
142
247
|
end
|
248
|
+
else
|
249
|
+
raise Error, "#{@config.app_name} supervisor isn't running"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# Status
|
255
|
+
#
|
256
|
+
|
257
|
+
desc "Show the current status of processes"
|
258
|
+
options do |opts, cli|
|
259
|
+
opts.on("--json", "Return the status as a JSON hash") do
|
260
|
+
cli.options[:json] = true
|
261
|
+
end
|
262
|
+
end
|
263
|
+
command def status
|
264
|
+
if running?
|
265
|
+
status = ControlClient.run(@config.sock_path, 'status')
|
266
|
+
if @options[:json]
|
267
|
+
puts status.to_json
|
268
|
+
else
|
269
|
+
require 'procodile/status_cli_output'
|
270
|
+
StatusCLIOutput.new(status).print_all
|
271
|
+
end
|
143
272
|
else
|
144
273
|
puts "#{@config.app_name} supervisor not running"
|
145
274
|
end
|
146
275
|
end
|
147
276
|
|
148
|
-
|
277
|
+
#
|
278
|
+
# Kill
|
279
|
+
#
|
280
|
+
|
281
|
+
desc "Forcefully kill all known processes"
|
282
|
+
command def kill
|
149
283
|
Dir[File.join(@config.pid_root, '*.pid')].each do |pid_path|
|
150
284
|
name = pid_path.split('/').last.gsub(/\.pid\z/, '')
|
151
285
|
pid = File.read(pid_path).to_i
|
@@ -195,8 +329,8 @@ module Procodile
|
|
195
329
|
end
|
196
330
|
|
197
331
|
def process_names_from_cli_option
|
198
|
-
if @
|
199
|
-
processes = @
|
332
|
+
if @options[:processes]
|
333
|
+
processes = @options[:processes].split(',')
|
200
334
|
if processes.empty?
|
201
335
|
raise Error, "No process names provided"
|
202
336
|
end
|
data/lib/procodile/config.rb
CHANGED
@@ -21,37 +21,33 @@ module Procodile
|
|
21
21
|
@process_list = nil
|
22
22
|
@options = nil
|
23
23
|
@process_options = nil
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
@local_options = nil
|
25
|
+
@local_process_options = nil
|
26
|
+
|
27
|
+
if @processes
|
28
|
+
process_list.each do |name, command|
|
29
|
+
if process = @processes[name]
|
30
|
+
# This command is already in our list. Add it.
|
31
|
+
if process.command != command
|
32
|
+
process.command = command
|
33
|
+
Procodile.log nil, 'system', "#{name} command has changed. Updated."
|
34
|
+
end
|
35
|
+
process.options = options_for_process(name)
|
35
36
|
else
|
36
|
-
|
37
|
+
Procodile.log nil, 'system', "#{name} has been added to the Procfile. Adding it."
|
38
|
+
@processes[name] = create_process(name, command, COLORS[@processes.size.divmod(COLORS.size)[1]])
|
37
39
|
end
|
38
|
-
else
|
39
|
-
Procodile.log nil, 'system', "#{name} has been added to the Procfile. Adding it."
|
40
|
-
@processes[name] = Process.new(self, name, command, process_options[name] || {})
|
41
|
-
@processes[name].log_color = COLORS[@processes.size.divmod(COLORS.size)[1]]
|
42
40
|
end
|
43
41
|
end
|
44
|
-
|
45
42
|
end
|
46
43
|
|
47
44
|
def app_name
|
48
|
-
@app_name ||= options['app_name'] || 'Procodile'
|
45
|
+
@app_name ||= local_options['app_name'] || options['app_name'] || 'Procodile'
|
49
46
|
end
|
50
47
|
|
51
48
|
def processes
|
52
49
|
@processes ||= process_list.each_with_index.each_with_object({}) do |((name, command), index), hash|
|
53
|
-
hash[name] =
|
54
|
-
hash[name].log_color = COLORS[index.divmod(COLORS.size)[1]]
|
50
|
+
hash[name] = create_process(name, command, COLORS[index.divmod(COLORS.size)[1]])
|
55
51
|
end
|
56
52
|
end
|
57
53
|
|
@@ -67,12 +63,32 @@ module Procodile
|
|
67
63
|
@process_options ||= options['processes'] || {}
|
68
64
|
end
|
69
65
|
|
66
|
+
def local_options
|
67
|
+
@local_options ||= File.exist?(local_options_path) ? YAML.load_file(local_options_path) : {}
|
68
|
+
end
|
69
|
+
|
70
|
+
def local_process_options
|
71
|
+
@local_process_options ||= local_options['processes'] || {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def options_for_process(name)
|
75
|
+
(process_options[name] || {}).merge(local_process_options[name] || {})
|
76
|
+
end
|
77
|
+
|
78
|
+
def environment_variables
|
79
|
+
(options['env'] || {}).merge(local_options['env'] || {})
|
80
|
+
end
|
81
|
+
|
82
|
+
def local_environment_variables
|
83
|
+
@local_environment_variables ||= local_options['env'] || {}
|
84
|
+
end
|
85
|
+
|
70
86
|
def pid_root
|
71
|
-
@pid_root ||= File.expand_path(options['pid_root'] || 'pids', @root)
|
87
|
+
@pid_root ||= File.expand_path(local_options['pid_root'] || options['pid_root'] || 'pids', @root)
|
72
88
|
end
|
73
89
|
|
74
90
|
def log_path
|
75
|
-
@log_path ||= File.expand_path(options['log_path'] || 'procodile.log', @root)
|
91
|
+
@log_path ||= File.expand_path(local_options['log_path'] || options['log_path'] || 'procodile.log', @root)
|
76
92
|
end
|
77
93
|
|
78
94
|
def sock_path
|
@@ -89,5 +105,15 @@ module Procodile
|
|
89
105
|
File.join(@root, 'Procfile.options')
|
90
106
|
end
|
91
107
|
|
108
|
+
def local_options_path
|
109
|
+
File.join(@root, 'Procfile.local')
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_process(name, command, log_color)
|
113
|
+
process = Process.new(self, name, command, options_for_process(name))
|
114
|
+
process.log_color = log_color
|
115
|
+
process
|
116
|
+
end
|
117
|
+
|
92
118
|
end
|
93
119
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'procodile/version'
|
2
3
|
|
3
4
|
module Procodile
|
4
5
|
class ControlSession
|
@@ -30,7 +31,7 @@ module Procodile
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def stop(options)
|
33
|
-
instances = @supervisor.stop(:processes => options['processes'])
|
34
|
+
instances = @supervisor.stop(:processes => options['processes'], :stop_supervisor => options['stop_supervisor'])
|
34
35
|
"200 " + instances.map(&:to_hash).to_json
|
35
36
|
end
|
36
37
|
|
@@ -44,23 +45,32 @@ module Procodile
|
|
44
45
|
"200"
|
45
46
|
end
|
46
47
|
|
48
|
+
def check_concurrency(options)
|
49
|
+
result = @supervisor.check_concurrency(:reload => options['reload'])
|
50
|
+
result = result.each_with_object({}) { |(type, instances), hash| hash[type] = instances.map(&:to_hash) }
|
51
|
+
"200 #{result.to_json}"
|
52
|
+
end
|
53
|
+
|
54
|
+
|
47
55
|
def status(options)
|
48
56
|
instances = {}
|
49
57
|
@supervisor.processes.each do |process, process_instances|
|
50
58
|
instances[process.name] = []
|
51
59
|
for instance in process_instances
|
52
|
-
instances[process.name] <<
|
53
|
-
:description => instance.description,
|
54
|
-
:pid => instance.pid,
|
55
|
-
:running => instance.running?,
|
56
|
-
:respawns => instance.respawns,
|
57
|
-
:command => instance.process.command
|
58
|
-
}
|
60
|
+
instances[process.name] << instance.to_hash
|
59
61
|
end
|
60
62
|
end
|
61
63
|
|
62
64
|
processes = @supervisor.processes.keys.map(&:to_hash)
|
63
|
-
result = {
|
65
|
+
result = {
|
66
|
+
:version => Procodile::VERSION,
|
67
|
+
:root => @supervisor.config.root,
|
68
|
+
:app_name => @supervisor.config.app_name,
|
69
|
+
:supervisor => @supervisor.to_hash,
|
70
|
+
:instances => instances,
|
71
|
+
:processes => processes,
|
72
|
+
:environment_variables => @supervisor.config.environment_variables
|
73
|
+
}
|
64
74
|
"200 #{result.to_json}"
|
65
75
|
end
|
66
76
|
|
data/lib/procodile/instance.rb
CHANGED
@@ -13,6 +13,7 @@ module Procodile
|
|
13
13
|
@id = id
|
14
14
|
@respawns = 0
|
15
15
|
@respawnable = true
|
16
|
+
@started_at = nil
|
16
17
|
end
|
17
18
|
|
18
19
|
#
|
@@ -22,6 +23,16 @@ module Procodile
|
|
22
23
|
"#{@process.name}.#{@id}"
|
23
24
|
end
|
24
25
|
|
26
|
+
#
|
27
|
+
# Return an array of environment variables that should be set
|
28
|
+
#
|
29
|
+
def environment_variables
|
30
|
+
@process.config.environment_variables.merge({
|
31
|
+
'PID_FILE' => self.pid_file_path,
|
32
|
+
'APP_ROOT' => @process.config.root
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
25
36
|
#
|
26
37
|
# Should this instance still be monitored by the supervisor?
|
27
38
|
#
|
@@ -64,7 +75,7 @@ module Procodile
|
|
64
75
|
#
|
65
76
|
# Start a new instance of this process
|
66
77
|
#
|
67
|
-
def start
|
78
|
+
def start(&block)
|
68
79
|
@stopping = false
|
69
80
|
existing_pid = self.pid_from_file
|
70
81
|
if running?(existing_pid)
|
@@ -72,6 +83,7 @@ module Procodile
|
|
72
83
|
# to monitor this process rather than spawning a new one.
|
73
84
|
@pid = existing_pid
|
74
85
|
Procodile.log(@process.log_color, description, "Already running with PID #{@pid}")
|
86
|
+
@started_at = File.mtime(self.pid_file_path)
|
75
87
|
nil
|
76
88
|
else
|
77
89
|
if self.process.log_path
|
@@ -84,10 +96,15 @@ module Procodile
|
|
84
96
|
end
|
85
97
|
|
86
98
|
Dir.chdir(@process.config.root)
|
87
|
-
@pid = ::Process.spawn(
|
99
|
+
@pid = ::Process.spawn(environment_variables, @process.command, :out => log_destination, :err => log_destination, :pgroup => true)
|
88
100
|
Procodile.log(@process.log_color, description, "Started with PID #{@pid}")
|
89
101
|
File.open(pid_file_path, 'w') { |f| f.write(@pid.to_s + "\n") }
|
90
102
|
::Process.detach(@pid)
|
103
|
+
@started_at = Time.now
|
104
|
+
|
105
|
+
if block_given?
|
106
|
+
block.call(self, return_value)
|
107
|
+
end
|
91
108
|
|
92
109
|
return_value
|
93
110
|
end
|
@@ -120,6 +137,7 @@ module Procodile
|
|
120
137
|
# started again
|
121
138
|
#
|
122
139
|
def on_stop
|
140
|
+
@started_at = nil
|
123
141
|
tidy
|
124
142
|
unmonitor
|
125
143
|
end
|
@@ -135,7 +153,7 @@ module Procodile
|
|
135
153
|
#
|
136
154
|
# Retarts the process using the appropriate method from the process configuraiton
|
137
155
|
#
|
138
|
-
def restart
|
156
|
+
def restart(&block)
|
139
157
|
Procodile.log(@process.log_color, description, "Restarting using #{@process.restart_mode} mode")
|
140
158
|
@restarting = true
|
141
159
|
update_pid
|
@@ -146,11 +164,11 @@ module Procodile
|
|
146
164
|
Procodile.log(@process.log_color, description, "Sent #{@process.restart_mode.upcase} signal to process #{@pid}")
|
147
165
|
else
|
148
166
|
Procodile.log(@process.log_color, description, "Process not running already. Starting it.")
|
149
|
-
start
|
167
|
+
start(&block)
|
150
168
|
end
|
151
169
|
when 'start-term'
|
152
170
|
old_process_pid = @pid
|
153
|
-
start
|
171
|
+
start(&block)
|
154
172
|
Procodile.log(@process.log_color, description, "Sent #{@process.term_signal} signal to old PID #{old_process_pid} (forgetting now)")
|
155
173
|
::Process.kill(@process.term_signal, old_process_pid)
|
156
174
|
when 'term-start'
|
@@ -158,7 +176,7 @@ module Procodile
|
|
158
176
|
Thread.new do
|
159
177
|
# Wait for this process to stop and when it has, run it.
|
160
178
|
sleep 0.5 while running?
|
161
|
-
start
|
179
|
+
start(&block)
|
162
180
|
end
|
163
181
|
end
|
164
182
|
ensure
|
@@ -259,7 +277,8 @@ module Procodile
|
|
259
277
|
:description => self.description,
|
260
278
|
:pid => self.pid,
|
261
279
|
:respawns => self.respawns,
|
262
|
-
:running => self.running
|
280
|
+
:running => self.running?,
|
281
|
+
:started_at => @started_at ? @started_at.to_i : nil
|
263
282
|
}
|
264
283
|
end
|
265
284
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Procodile
|
2
|
+
class StatusCLIOutput
|
3
|
+
|
4
|
+
def initialize(status)
|
5
|
+
@status = status
|
6
|
+
end
|
7
|
+
|
8
|
+
def print_all
|
9
|
+
print_header
|
10
|
+
print_processes
|
11
|
+
end
|
12
|
+
|
13
|
+
def print_header
|
14
|
+
puts "Procodile Version " + @status['version'].to_s.color(34)
|
15
|
+
puts "Application Root " + "#{@status['root']}".color(34)
|
16
|
+
puts "Supervisor PID " + "#{@status['supervisor']['pid']}".color(34)
|
17
|
+
if time = @status['supervisor']['started_at']
|
18
|
+
time = Time.at(time)
|
19
|
+
puts "Started " + "#{time.to_s}".color(34)
|
20
|
+
end
|
21
|
+
if @status['environment_variables'] && !@status['environment_variables'].empty?
|
22
|
+
@status['environment_variables'].each_with_index do |(key, value), index|
|
23
|
+
if index == 0
|
24
|
+
print "Environment Vars "
|
25
|
+
else
|
26
|
+
print " "
|
27
|
+
end
|
28
|
+
print key.color(34)
|
29
|
+
puts " " + value.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_processes
|
35
|
+
puts
|
36
|
+
@status['processes'].each_with_index do |process, index|
|
37
|
+
puts unless index == 0
|
38
|
+
puts "|| ".color(process['log_color']) + process['name'].color(process['log_color'])
|
39
|
+
puts "||".color(process['log_color']) + " Quantity " + process['quantity'].to_s
|
40
|
+
puts "||".color(process['log_color']) + " Command " + process['command']
|
41
|
+
puts "||".color(process['log_color']) + " Respawning " + "#{process['max_respawns']} every #{process['respawn_window']} seconds"
|
42
|
+
puts "||".color(process['log_color']) + " Restart mode " + process['restart_mode']
|
43
|
+
puts "||".color(process['log_color']) + " Log path " + (process['log_path'] || "none specified")
|
44
|
+
instances = @status['instances'][process['name']]
|
45
|
+
if instances.empty?
|
46
|
+
puts "||".color(process['log_color']) + " No processes running."
|
47
|
+
else
|
48
|
+
instances.each do |instance|
|
49
|
+
print "|| => ".color(process['log_color']) + instance['description'].to_s.ljust(17, ' ').color(process['log_color'])
|
50
|
+
if instance['running']
|
51
|
+
print 'Running'.color("32")
|
52
|
+
else
|
53
|
+
print 'Stopped'.color("31")
|
54
|
+
end
|
55
|
+
print " " + formatted_timestamp(instance['started_at']).ljust(10, ' ')
|
56
|
+
print " pid: " + instance['pid'].to_s.ljust(7, ' ')
|
57
|
+
print " respawns: " + instance['respawns'].to_s.ljust(7, ' ')
|
58
|
+
puts
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def formatted_timestamp(timestamp)
|
67
|
+
return '' if timestamp.nil?
|
68
|
+
timestamp = Time.at(timestamp)
|
69
|
+
if timestamp > (Time.now - (60 * 60 * 24))
|
70
|
+
timestamp.strftime("%H:%M")
|
71
|
+
else
|
72
|
+
timestamp.strftime("%d/%m/%Y")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
data/lib/procodile/supervisor.rb
CHANGED
@@ -5,6 +5,7 @@ module Procodile
|
|
5
5
|
|
6
6
|
attr_reader :config
|
7
7
|
attr_reader :processes
|
8
|
+
attr_reader :started_at
|
8
9
|
|
9
10
|
def initialize(config, run_options = {})
|
10
11
|
@config = config
|
@@ -13,7 +14,7 @@ module Procodile
|
|
13
14
|
@readers = {}
|
14
15
|
@signal_handler = SignalHandler.new('TERM', 'USR1', 'USR2', 'INT', 'HUP')
|
15
16
|
@signal_handler.register('TERM') { stop_supervisor }
|
16
|
-
@signal_handler.register('INT') { stop }
|
17
|
+
@signal_handler.register('INT') { stop(:stop_supervisor => true) }
|
17
18
|
@signal_handler.register('USR1') { restart }
|
18
19
|
@signal_handler.register('USR2') { status }
|
19
20
|
@signal_handler.register('HUP') { reload_config }
|
@@ -30,10 +31,12 @@ module Procodile
|
|
30
31
|
end
|
31
32
|
start_processes(options[:processes])
|
32
33
|
watch_for_output
|
34
|
+
@started_at = Time.now
|
33
35
|
loop { supervise; sleep 3 }
|
34
36
|
end
|
35
37
|
|
36
38
|
def start_processes(types = [])
|
39
|
+
reload_config
|
37
40
|
Array.new.tap do |instances_started|
|
38
41
|
@config.processes.each do |name, process|
|
39
42
|
next if types && !types.include?(name.to_s) # Not a process we want
|
@@ -45,17 +48,19 @@ module Procodile
|
|
45
48
|
end
|
46
49
|
|
47
50
|
def stop(options = {})
|
51
|
+
if options[:stop_supervisor]
|
52
|
+
@run_options[:stop_when_none] = true
|
53
|
+
end
|
54
|
+
|
48
55
|
Array.new.tap do |instances_stopped|
|
49
56
|
if options[:processes].nil?
|
50
|
-
return if @stopping
|
51
|
-
@stopping = true
|
52
57
|
Procodile.log nil, "system", "Stopping all #{@config.app_name} processes"
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
+
@processes.each do |_, instances|
|
59
|
+
instances.each do |instance|
|
60
|
+
instance.stop
|
61
|
+
instances_stopped << instance
|
58
62
|
end
|
63
|
+
end
|
59
64
|
else
|
60
65
|
instances = process_names_to_instances(options[:processes])
|
61
66
|
Procodile.log nil, "system", "Stopping #{instances.size} process(es)"
|
@@ -68,23 +73,25 @@ module Procodile
|
|
68
73
|
end
|
69
74
|
|
70
75
|
def restart(options = {})
|
71
|
-
|
76
|
+
reload_config
|
72
77
|
Array.new.tap do |instances_restarted|
|
73
78
|
if options[:processes].nil?
|
74
79
|
Procodile.log nil, "system", "Restarting all #{@config.app_name} processes"
|
75
80
|
@processes.each do |_, instances|
|
76
81
|
instances.each do |instance|
|
77
|
-
instance.restart
|
82
|
+
instance.restart { |_, io| add_reader(instance, io) }
|
78
83
|
instances_restarted << instance
|
79
84
|
end
|
80
85
|
end
|
86
|
+
instances_restarted.push(*check_instance_quantities[:started])
|
81
87
|
else
|
82
88
|
instances = process_names_to_instances(options[:processes])
|
83
89
|
Procodile.log nil, "system", "Restarting #{instances.size} process(es)"
|
84
90
|
instances.each do |instance|
|
85
|
-
instance.restart
|
91
|
+
instance.restart { |_, io| add_reader(instance, io) }
|
86
92
|
instances_restarted << instance
|
87
93
|
end
|
94
|
+
instances_restarted.push(*check_instance_quantities(options[:processes])[:started])
|
88
95
|
end
|
89
96
|
end
|
90
97
|
end
|
@@ -116,29 +123,66 @@ module Procodile
|
|
116
123
|
end
|
117
124
|
end
|
118
125
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
126
|
+
if @run_options[:stop_when_none]
|
127
|
+
# If the processes go away, we can stop the supervisor now
|
128
|
+
if @processes.all? { |_,instances| instances.size == 0 }
|
129
|
+
Procodile.log nil, "system", "All processes have stopped"
|
130
|
+
stop_supervisor
|
131
|
+
end
|
123
132
|
end
|
124
133
|
end
|
125
134
|
|
126
135
|
def reload_config
|
127
136
|
Procodile.log nil, "system", "Reloading configuration"
|
128
137
|
@config.reload
|
129
|
-
|
138
|
+
end
|
139
|
+
|
140
|
+
def check_concurrency(options = {})
|
141
|
+
Procodile.log nil, "system", "Checking process concurrency"
|
142
|
+
reload_config unless options[:reload] == false
|
143
|
+
result = check_instance_quantities
|
144
|
+
if result[:started].empty? && result[:stopped].empty?
|
145
|
+
Procodile.log nil, "system", "Process concurrency looks good"
|
146
|
+
else
|
147
|
+
unless result[:started].empty?
|
148
|
+
Procodile.log nil, "system", "Concurrency check started #{result[:started].map(&:description).join(', ')}"
|
149
|
+
end
|
150
|
+
|
151
|
+
unless result[:stopped].empty?
|
152
|
+
Procodile.log nil, "system", "Concurrency check stopped #{result[:stopped].map(&:description).join(', ')}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
result
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_hash
|
159
|
+
{
|
160
|
+
:started_at => @started_at ? @started_at.to_i : nil,
|
161
|
+
:pid => ::Process.pid
|
162
|
+
}
|
130
163
|
end
|
131
164
|
|
132
165
|
private
|
133
166
|
|
167
|
+
def add_reader(instance, io)
|
168
|
+
return unless io
|
169
|
+
@readers[io] = instance
|
170
|
+
@signal_handler.notice
|
171
|
+
end
|
172
|
+
|
134
173
|
def watch_for_output
|
135
174
|
Thread.new do
|
136
175
|
loop do
|
137
176
|
io = IO.select([@signal_handler.pipe[:reader]] + @readers.keys, nil, nil, 30)
|
138
177
|
@signal_handler.handle
|
178
|
+
|
139
179
|
if io
|
140
180
|
io.first.each do |reader|
|
141
|
-
|
181
|
+
if reader == @signal_handler.pipe[:reader]
|
182
|
+
@signal_handler.pipe[:reader].read_nonblock(999) rescue nil
|
183
|
+
next
|
184
|
+
end
|
185
|
+
|
142
186
|
if reader.eof?
|
143
187
|
@readers.delete(reader)
|
144
188
|
else
|
@@ -155,17 +199,20 @@ module Procodile
|
|
155
199
|
end
|
156
200
|
end
|
157
201
|
|
158
|
-
def check_instance_quantities
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
202
|
+
def check_instance_quantities(processes = nil)
|
203
|
+
{:started => [], :stopped => []}.tap do |status|
|
204
|
+
@processes.each do |process, instances|
|
205
|
+
next if processes && !processes.include?(process.name)
|
206
|
+
if instances.size > process.quantity
|
207
|
+
quantity_to_stop = instances.size - process.quantity
|
208
|
+
Procodile.log nil, "system", "Stopping #{quantity_to_stop} #{process.name} process(es)"
|
209
|
+
status[:stopped] = instances.last(quantity_to_stop).each(&:stop)
|
210
|
+
elsif instances.size < process.quantity
|
211
|
+
quantity_needed = process.quantity - instances.size
|
212
|
+
start_id = instances.last ? instances.last.id + 1 : 1
|
213
|
+
Procodile.log nil, "system", "Starting #{quantity_needed} more #{process.name} process(es) (start with #{start_id})"
|
214
|
+
status[:started] = start_instances(process.generate_instances(quantity_needed, start_id))
|
215
|
+
end
|
169
216
|
end
|
170
217
|
end
|
171
218
|
end
|
@@ -175,8 +222,7 @@ module Procodile
|
|
175
222
|
if @run_options[:brittle]
|
176
223
|
instance.respawnable = false
|
177
224
|
end
|
178
|
-
io
|
179
|
-
@readers[io] = instance if io
|
225
|
+
instance.start { |_, io| add_reader(instance, io) }
|
180
226
|
@processes[instance.process] ||= []
|
181
227
|
@processes[instance.process] << instance
|
182
228
|
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.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Cooke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -34,8 +34,6 @@ extra_rdoc_files: []
|
|
34
34
|
files:
|
35
35
|
- bin/procodile
|
36
36
|
- lib/procodile.rb
|
37
|
-
- lib/procodile/capistrano2.rb
|
38
|
-
- lib/procodile/capistrano3.rb
|
39
37
|
- lib/procodile/cli.rb
|
40
38
|
- lib/procodile/config.rb
|
41
39
|
- lib/procodile/control_client.rb
|
@@ -46,7 +44,9 @@ files:
|
|
46
44
|
- lib/procodile/logger.rb
|
47
45
|
- lib/procodile/process.rb
|
48
46
|
- lib/procodile/signal_handler.rb
|
47
|
+
- lib/procodile/status_cli_output.rb
|
49
48
|
- lib/procodile/supervisor.rb
|
49
|
+
- lib/procodile/version.rb
|
50
50
|
homepage: https://github.com/adamcooke/procodile
|
51
51
|
licenses:
|
52
52
|
- MIT
|
@@ -1,39 +0,0 @@
|
|
1
|
-
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
-
|
3
|
-
namespace :procodile do
|
4
|
-
|
5
|
-
task :start, :roles => fetch(:procodile_roles, [:app]) do
|
6
|
-
run procodile_command('start')
|
7
|
-
end
|
8
|
-
|
9
|
-
task :stop, :roles => fetch(:procodile_roles, [:app]) do
|
10
|
-
run procodile_command('stop')
|
11
|
-
end
|
12
|
-
|
13
|
-
task :restart, :roles => fetch(:procodile_roles, [:app]) do
|
14
|
-
run procodile_command('restart')
|
15
|
-
end
|
16
|
-
|
17
|
-
task :status, :roles => fetch(:procodile_roles, [:app]) do
|
18
|
-
run procodile_command('status')
|
19
|
-
end
|
20
|
-
|
21
|
-
after 'deploy:start', 'procodile:start'
|
22
|
-
after 'deploy:stop', 'procodile:stop'
|
23
|
-
after 'deploy:restart', 'procodile:restart'
|
24
|
-
|
25
|
-
def procodile_command(command, options = "")
|
26
|
-
binary = fetch(:procodile_binary, 'procodile')
|
27
|
-
if processes = fetch(:processes, nil)
|
28
|
-
options = "-p #{processes} " + options
|
29
|
-
end
|
30
|
-
command = "#{binary} #{command} -r #{current_path} #{options}"
|
31
|
-
if user = fetch(:procodile_user, nil)
|
32
|
-
"sudo -u #{user} #{command}"
|
33
|
-
else
|
34
|
-
command
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
namespace :procodile do
|
2
|
-
desc 'Start procodile processes'
|
3
|
-
task :start do
|
4
|
-
on roles(fetch(:procodile_roles, [:app])) do
|
5
|
-
execute procodile_command(:start)
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
desc 'Stop procodile processes'
|
10
|
-
task :stop do
|
11
|
-
on roles(fetch(:procodile_roles, [:app])) do
|
12
|
-
execute procodile_command(:stop)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
desc 'Restart procodile processes'
|
17
|
-
task :restart do
|
18
|
-
on roles(fetch(:procodile_roles, [:app])) do
|
19
|
-
execute procodile_command(:restart)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
after 'deploy:start', "procodile:start"
|
24
|
-
after 'deploy:stop', "procodile:stop"
|
25
|
-
after 'deploy:restart', "procodile:restart"
|
26
|
-
|
27
|
-
def procodile_command(command, options = "")
|
28
|
-
binary = fetch(:procodile_binary, 'procodile')
|
29
|
-
if processes = fetch(:processes, nil)
|
30
|
-
options = "-p #{processes} " + options
|
31
|
-
end
|
32
|
-
command = "#{binary} #{command} -r #{current_path} #{options}"
|
33
|
-
if user = fetch(:procodile_user, nil)
|
34
|
-
"sudo -u #{user} #{command}"
|
35
|
-
else
|
36
|
-
command
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|