procodile 1.0.2 → 1.0.3
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 +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
|