unicorn-ctl 0.1.0
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.
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +67 -0
- data/bin/unicornctl +480 -0
- metadata +83 -0
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Oleksiy Kovyrin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
## unicornctl - unicorn/rainbows control script
|
2
|
+
|
3
|
+
`unicornctl` is a simple and easy to use console tool for managing ruby applications using unicorn or
|
4
|
+
rainbows application server. The tool provides a set of reliable commands to start/stop/restart
|
5
|
+
unicorn instances or upgrade them without or with minimal downtime. `unicornctl` script could easily
|
6
|
+
be used as a base for a startup script for unix operating systems (see examples directory for a
|
7
|
+
Redhat-style startup script example).
|
8
|
+
|
9
|
+
Please note, that this is still an alpha-quality software and it could have many issues. If you try it and
|
10
|
+
find any problems, feel free to report them using [Github Issues page](https://github.com/swiftype/unicorn-ctl/issues).
|
11
|
+
Pull requests are welcome too!
|
12
|
+
|
13
|
+
### Installation
|
14
|
+
|
15
|
+
The script comes packaged as a rubygem, so installation procedure is as simple as running the
|
16
|
+
following command:
|
17
|
+
|
18
|
+
sudo gem install unicorn-ctl
|
19
|
+
|
20
|
+
After installation you should have an access to the `unicornctl` console command.
|
21
|
+
|
22
|
+
### Usage Instructions
|
23
|
+
|
24
|
+
Here is the help output from the `unicornctl` command:
|
25
|
+
|
26
|
+
```
|
27
|
+
Usage: unicornctl [options] <command>
|
28
|
+
Valid commands: start, stop, force-stop, restart, force-restart, upgrade
|
29
|
+
Options:
|
30
|
+
--app-dir=dir | -d dir Base directory for the application (required)
|
31
|
+
--environment=name | -e name RACK_ENV to use for the app (default: development)
|
32
|
+
--health-check-url=url | -H url Health check URL used to make sure the app has started
|
33
|
+
--health-check-content=string | -C string Health check expected content (default: just check for HTTP 200 OK)
|
34
|
+
--health-check-timeout=sec | -T sec Individual health check timeout (default: 5 sec)
|
35
|
+
--timeout=sec | -t sec Operation (start/stop/etc) timeout (default: 30 sec)
|
36
|
+
--unicorn-config=file | -c file Unicorn config file to use, absolute or relative path (default: shared/unicorn.rb)
|
37
|
+
--rackup-config=file | -r file Rackup config file to use, absolute or relative path (default: current/config.ru)
|
38
|
+
--pid-file=file | -p file PID-file unicorn is configured to use (default: shared/pids/unicorn.pid)
|
39
|
+
--rainbows | -R Use rainbows to start the app (default: use unicorn)
|
40
|
+
--help | -h This help
|
41
|
+
```
|
42
|
+
|
43
|
+
The following command are supported at the moment:
|
44
|
+
|
45
|
+
* `start` - starts a unicorn application and performs a health check if health check URL is available.
|
46
|
+
If the server is already running, only the health check would be performed.
|
47
|
+
* `stop` - gracefully stops a unicorn application by sending a `QUIT` signal to the master process
|
48
|
+
and waiting for a specified amount of time (30 sec by default) for the process and all of its
|
49
|
+
children to shut down properly. If the process fails to stop in a given time, it is killed
|
50
|
+
with a `KILL` signal.
|
51
|
+
* `force-stop` - forcefully shuts down a unicorn application by sending a `TERM` signal to it and
|
52
|
+
waiting for the process to shut down quickly. If the process fails to stop in a given time, it is
|
53
|
+
killed with a `KILL` signal.
|
54
|
+
* `restart` - gracefully shuts a unicorn application down and then starts it back up (performing a
|
55
|
+
helth check if possible).
|
56
|
+
* `force-restart` - forcefully shuts a unicorn application down and then starts it back up
|
57
|
+
(performing a helth check if possible).
|
58
|
+
* `upgrade` - zero or minimal downtime restart option for a unicorn application. Performs a set of
|
59
|
+
steps to start a new copy of the application, test it and then gracefully shut down the old copy.
|
60
|
+
If the graceful restart fails for any reason, the application is forcefully restarted.
|
61
|
+
|
62
|
+
For more information on unicorn procss management you could check their
|
63
|
+
[official documentation page](http://unicorn.bogomips.org/SIGNALS.html).
|
64
|
+
|
65
|
+
### License
|
66
|
+
|
67
|
+
The code is distributed under the MIT license. For more details, see the LICENSE.txt file.
|
data/bin/unicornctl
ADDED
@@ -0,0 +1,480 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'shellwords'
|
6
|
+
require 'getoptlong'
|
7
|
+
require 'httparty'
|
8
|
+
|
9
|
+
VALID_COMMANDS = %w[
|
10
|
+
start
|
11
|
+
stop
|
12
|
+
force-stop
|
13
|
+
restart
|
14
|
+
force-restart
|
15
|
+
upgrade
|
16
|
+
]
|
17
|
+
|
18
|
+
#---------------------------------------------------------------------------------------------------
|
19
|
+
# Make sure our console output is synchronous
|
20
|
+
STDOUT.sync = true
|
21
|
+
STDERR.sync = true
|
22
|
+
|
23
|
+
# Trap interrupts to quit cleanly. See
|
24
|
+
# https://twitter.com/mitchellh/status/283014103189053442
|
25
|
+
Signal.trap("INT") { exit 1 }
|
26
|
+
|
27
|
+
#---------------------------------------------------------------------------------------------------
|
28
|
+
def app_file_path(app_dir, config_name)
|
29
|
+
return config_name if config_name =~ /^\//
|
30
|
+
return File.join(app_dir, config_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def unicorn_pid_file(options)
|
34
|
+
if options[:pid_file]
|
35
|
+
app_file_path(options[:app_dir], options[:pid_file])
|
36
|
+
else
|
37
|
+
File.join(options[:app_dir], 'shared', 'pids', "#{options[:unicorn_bin]}.pid")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def unicorn_config_file(options)
|
42
|
+
if options[:unicorn_config]
|
43
|
+
app_file_path(options[:app_dir], options[:unicorn_config])
|
44
|
+
else
|
45
|
+
File.join(options[:app_dir], 'shared', "#{options[:unicorn_bin]}.rb")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def rackup_config_file(options)
|
50
|
+
if options[:rackup_config]
|
51
|
+
app_file_path(options[:app_dir], options[:rackup_config])
|
52
|
+
else
|
53
|
+
File.join(options[:app_dir], 'current', 'config.ru')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def escape(param)
|
58
|
+
Shellwords.escape(param)
|
59
|
+
end
|
60
|
+
|
61
|
+
#-------------------------------------------------------------------------------------------
|
62
|
+
# Check if process is still running
|
63
|
+
def pid_running?(pid)
|
64
|
+
begin
|
65
|
+
Process.kill(0, pid)
|
66
|
+
return true
|
67
|
+
rescue Errno::ESRCH
|
68
|
+
return false
|
69
|
+
rescue ::Exception # for example on EPERM (process exists but does not belong to us)
|
70
|
+
return true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_signal(signal, pid)
|
75
|
+
Process.kill(signal, pid)
|
76
|
+
rescue => e
|
77
|
+
puts "WARNING: Failed to send signal #{signal} to #{pid}: #{e}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_pid(pid_file)
|
81
|
+
File.read(pid_file).strip.to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
def wait_for_pid_to_die(pid, timeout)
|
85
|
+
print "Waiting for the process to stop: "
|
86
|
+
start_time = Time.now
|
87
|
+
while Time.now - start_time < timeout
|
88
|
+
print "."
|
89
|
+
break unless pid_running?(pid)
|
90
|
+
sleep(1)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def stop_unicorn_process(pid, timeout, graceful)
|
95
|
+
signal = graceful ? 'QUIT' : 'TERM'
|
96
|
+
puts "Sending #{signal} signal to process with pid=#{pid}..."
|
97
|
+
send_signal(signal, pid)
|
98
|
+
|
99
|
+
wait_for_pid_to_die(pid, timeout)
|
100
|
+
|
101
|
+
if pid_running?(pid)
|
102
|
+
puts " Failed to stop, killing!"
|
103
|
+
kill_tree(pid)
|
104
|
+
else
|
105
|
+
puts " Done!"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#---------------------------------------------------------------------------------------------------
|
110
|
+
# Kills a process and all of its descendants
|
111
|
+
def kill_tree(pid)
|
112
|
+
# FIXME: Implrement a real killtree
|
113
|
+
send_signal(9, pid)
|
114
|
+
end
|
115
|
+
|
116
|
+
#---------------------------------------------------------------------------------------------------
|
117
|
+
# Performs a health check on an http endpoint
|
118
|
+
def check_app_health(options)
|
119
|
+
puts "Checking service health with URL: #{options[:check_url]}"
|
120
|
+
|
121
|
+
start_time = Time.now
|
122
|
+
while Time.now - start_time < options[:timeout]
|
123
|
+
sleep(1)
|
124
|
+
|
125
|
+
response = begin
|
126
|
+
HTTParty.get(options[:check_url], :timeout => options[:check_timeout])
|
127
|
+
rescue Timeout::Error => e
|
128
|
+
puts "Health check timed out after #{options[:check_timeout]} seconds. Retrying..."
|
129
|
+
next
|
130
|
+
end
|
131
|
+
|
132
|
+
if response.code.to_i / 100 == 2
|
133
|
+
puts "Health check succeeded with code: #{response.code}"
|
134
|
+
if options[:check_content]
|
135
|
+
if response.body.match(options[:check_content])
|
136
|
+
puts "Content check succeeded, found content in response body: #{options[:check_content]}"
|
137
|
+
return true
|
138
|
+
else
|
139
|
+
puts "ERROR: Could not find content in response body: #{options[:check_content]}. Retrying."
|
140
|
+
next
|
141
|
+
end
|
142
|
+
end
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
|
146
|
+
puts "Health check failed with status #{response.code}. Retrying..."
|
147
|
+
end
|
148
|
+
|
149
|
+
puts "ERROR: Health check has been failing for #{Time.now - start_time} seconds, giving up now!"
|
150
|
+
return false
|
151
|
+
end
|
152
|
+
|
153
|
+
#---------------------------------------------------------------------------------------------------
|
154
|
+
def start_application!(options)
|
155
|
+
# Check pid file
|
156
|
+
pid_file = unicorn_pid_file(options)
|
157
|
+
if File.exists?(pid_file)
|
158
|
+
pid = read_pid(pid_file)
|
159
|
+
|
160
|
+
if pid_running?(pid)
|
161
|
+
puts "OK: The app is already running"
|
162
|
+
|
163
|
+
# If we have a health check url, let's check it
|
164
|
+
if options[:check_url]
|
165
|
+
exit(1) unless check_app_health(options)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Done
|
169
|
+
exit(0)
|
170
|
+
else
|
171
|
+
puts "WARNING: Slate pid file found, removing it: #{pid_file}"
|
172
|
+
FileUtils.rm(pid_file)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Get unicorn config
|
177
|
+
unicorn_config = unicorn_config_file(options)
|
178
|
+
unless File.readable?(unicorn_config)
|
179
|
+
puts "ERROR: Could not find unicorn config: #{unicorn_config}"
|
180
|
+
exit(1)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Get rackup config
|
184
|
+
rackup_config = rackup_config_file(options)
|
185
|
+
unless File.readable?(rackup_config)
|
186
|
+
puts "ERROR: Could not find rackup config: #{rackup_config}"
|
187
|
+
exit(1)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Compose unicorn startup command
|
191
|
+
command = "cd #{escape options[:app_dir]}/current && bundle exec #{options[:unicorn_bin]} " <<
|
192
|
+
"--env #{escape options[:env]} --daemonize --config-file #{escape unicorn_config} "
|
193
|
+
"#{escape rackup_config}"
|
194
|
+
|
195
|
+
# Run startup command
|
196
|
+
puts "Starting unicorn..."
|
197
|
+
res = system(command)
|
198
|
+
unless res
|
199
|
+
puts "ERROR: Failed to start unicorn command: #{command}"
|
200
|
+
exit(1)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Wait for a few seconds...
|
204
|
+
sleep(2)
|
205
|
+
|
206
|
+
# Check pid file
|
207
|
+
unless File.exists?(pid_file)
|
208
|
+
puts "ERROR: Even though startup command succeeded, there is no pid file: #{pid_file}"
|
209
|
+
exit(1)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Check to make sure the process exists
|
213
|
+
pid = File.read(pid_file).strip
|
214
|
+
unless pid_running?(pid)
|
215
|
+
puts "ERROR: Even though startup command succeeded and pid file exists, there is no process with pid=#{pid}"
|
216
|
+
exit(1)
|
217
|
+
end
|
218
|
+
|
219
|
+
# If we have a health check url, let's check it
|
220
|
+
if options[:check_url]
|
221
|
+
exit(1) unless check_app_health(options)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Ok, we're good
|
225
|
+
puts "Started! PID=#{pid}"
|
226
|
+
exit(0)
|
227
|
+
end
|
228
|
+
|
229
|
+
#---------------------------------------------------------------------------------------------------
|
230
|
+
def stop_application!(options, graceful = false)
|
231
|
+
# Check pid file
|
232
|
+
pid_file = unicorn_pid_file(options)
|
233
|
+
unless File.exists?(pid_file)
|
234
|
+
puts "OK: The process is not running"
|
235
|
+
exit(0)
|
236
|
+
end
|
237
|
+
|
238
|
+
pid = read_pid(pid_file)
|
239
|
+
if pid_running?(pid)
|
240
|
+
stop_unicorn_process(pid, options[:timeout], graceful)
|
241
|
+
else
|
242
|
+
puts "WARNING: Slate pid file found, removing it: #{pid_file}"
|
243
|
+
FileUtils.rm(pid_file)
|
244
|
+
end
|
245
|
+
|
246
|
+
puts "Stopped!"
|
247
|
+
exit(0)
|
248
|
+
end
|
249
|
+
|
250
|
+
#---------------------------------------------------------------------------------------------------
|
251
|
+
def restart_application!(options, graceful)
|
252
|
+
# Check if the app is running and stop it if needed
|
253
|
+
pid_file = unicorn_pid_file(options)
|
254
|
+
if File.exists?(pid_file)
|
255
|
+
pid = read_pid(pid_file)
|
256
|
+
if pid_running?(pid)
|
257
|
+
stop_unicorn_process(pid, options[:timeout], graceful)
|
258
|
+
else
|
259
|
+
puts "WARNING: Slate pid file found, removing it: #{pid_file}"
|
260
|
+
FileUtils.rm(pid_file)
|
261
|
+
end
|
262
|
+
|
263
|
+
puts "Stopped!"
|
264
|
+
else
|
265
|
+
puts "The process is not running"
|
266
|
+
end
|
267
|
+
|
268
|
+
# Start the app
|
269
|
+
start_application!(options)
|
270
|
+
end
|
271
|
+
|
272
|
+
#---------------------------------------------------------------------------------------------------
|
273
|
+
def upgrade_application!(options)
|
274
|
+
pid_file = unicorn_pid_file(options)
|
275
|
+
old_pid_file = unicorn_pid_file(options) + '.oldbin'
|
276
|
+
|
277
|
+
# Make sure there is no old pid file (which we could have if an upgrade failed mid-way)
|
278
|
+
if File.exists?(old_pid_file)
|
279
|
+
puts "WARNING: Old pid file exists: #{old_pid_file}"
|
280
|
+
|
281
|
+
pid = read_pid(old_pid_file)
|
282
|
+
if pid_running?(pid)
|
283
|
+
puts "WARNING: Old binary is still running, shutting it down"
|
284
|
+
stop_unicorn_process(pid, options[:timeout], false)
|
285
|
+
else
|
286
|
+
puts "WARNING: Removing stale pid file: #{old_pid_file}"
|
287
|
+
FileUtils.rm(old_pid_file)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Now let's see if the app is actually running
|
292
|
+
unless File.exists?(pid_file)
|
293
|
+
puts "WARNING: No pid file found: #{pid_file}. Trying to do a cold startup procedure..."
|
294
|
+
start_application!(options)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Get current pid and check if it is up
|
298
|
+
old_pid = read_pid(pid_file)
|
299
|
+
|
300
|
+
# If the app is down, just do a normal cold startup procedure
|
301
|
+
unless pid_running?(old_pid)
|
302
|
+
puts "WARNING: Stale pid file found: #{pid_file}. Trying to do a cold startup procedure..."
|
303
|
+
start_application!(options)
|
304
|
+
end
|
305
|
+
|
306
|
+
# The app is running, let's try to do the upgrade:
|
307
|
+
# Ask old master to start a new binary and move itself into the old state
|
308
|
+
puts "Sending USR2 signal to old master: #{old_pid}..."
|
309
|
+
send_signal('USR2', old_pid)
|
310
|
+
|
311
|
+
puts "Waiting for the new master to replace the old one..."
|
312
|
+
|
313
|
+
# Wait for the new master to start
|
314
|
+
start_time = Time.now
|
315
|
+
new_started = false
|
316
|
+
while Time.now - start_time < options[:timeout]
|
317
|
+
sleep(1)
|
318
|
+
new_pid = File.exists?(pid_file) ? read_pid(pid_file) : nil
|
319
|
+
if new_pid != old_pid
|
320
|
+
new_started = true
|
321
|
+
break
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# If we failed to see the new master started, let's try to do a cold restart
|
326
|
+
unless new_started
|
327
|
+
puts "WARNING: New master didn't start in #{options[:timeout]} seconds, trying to do a cold restart..."
|
328
|
+
stop_unicorn_process(old_pid, options[:timeout], false)
|
329
|
+
start_application!(options)
|
330
|
+
end
|
331
|
+
|
332
|
+
# We have the new master
|
333
|
+
new_pid = read_pid(pid_file)
|
334
|
+
puts "New master detected with pid=#{new_pid}"
|
335
|
+
|
336
|
+
# If we have a health check url, let's check it
|
337
|
+
if options[:check_url]
|
338
|
+
if check_app_health(options)
|
339
|
+
puts "Health check succeeded on the new master!"
|
340
|
+
else
|
341
|
+
puts "ERROR: Failed to verify health of the new master, nuking everything and trying a cold start..."
|
342
|
+
stop_unicorn_process(new_pid, 1, false)
|
343
|
+
stop_unicorn_process(old_pid, 1, false)
|
344
|
+
start_application!(options)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Now let's shut down the old master
|
349
|
+
puts "Stopping old unicorn master: #{old_pid}"
|
350
|
+
stop_unicorn_process(old_pid, options[:timeout], true)
|
351
|
+
|
352
|
+
# All done!
|
353
|
+
puts "OK: Upgrade is done successfully!"
|
354
|
+
end
|
355
|
+
|
356
|
+
#---------------------------------------------------------------------------------------------------
|
357
|
+
def show_help(error = nil)
|
358
|
+
puts "ERROR: #{error}\n\n" if error
|
359
|
+
|
360
|
+
puts "Usage: #{$0} [options] <command>"
|
361
|
+
puts "Valid commands: #{VALID_COMMANDS.join(', ')}"
|
362
|
+
puts 'Options:'
|
363
|
+
puts ' --app-dir=dir | -d dir Base directory for the application (required)'
|
364
|
+
puts ' --environment=name | -e name RACK_ENV to use for the app (default: development)'
|
365
|
+
puts ' --health-check-url=url | -H url Health check URL used to make sure the app has started'
|
366
|
+
puts ' --health-check-content=string | -C string Health check expected content (default: just check for HTTP 200 OK)'
|
367
|
+
puts ' --health-check-timeout=sec | -T sec Individual health check timeout (default: 5 sec)'
|
368
|
+
puts ' --timeout=sec | -t sec Operation (start/stop/etc) timeout (default: 30 sec)'
|
369
|
+
puts ' --unicorn-config=file | -c file Unicorn config file to use, absolute or relative path (default: shared/unicorn.rb)'
|
370
|
+
puts ' --rackup-config=file | -r file Rackup config file to use, absolute or relative path (default: current/config.ru)'
|
371
|
+
puts ' --pid-file=file | -p file PID-file unicorn is configured to use (default: shared/pids/unicorn.pid)'
|
372
|
+
puts ' --rainbows | -R Use rainbows to start the app (default: use unicorn)'
|
373
|
+
puts ' --help | -h This help'
|
374
|
+
puts
|
375
|
+
exit(error ? 1 : 0)
|
376
|
+
end
|
377
|
+
|
378
|
+
#---------------------------------------------------------------------------------------------------
|
379
|
+
# Parse options
|
380
|
+
opts = GetoptLong.new(
|
381
|
+
[ '--app-dir', '-d', GetoptLong::REQUIRED_ARGUMENT ],
|
382
|
+
[ '--environment', '-e', GetoptLong::REQUIRED_ARGUMENT ],
|
383
|
+
[ '--health-check-url', '-U', GetoptLong::REQUIRED_ARGUMENT ],
|
384
|
+
[ '--health-check-content', '-C', GetoptLong::REQUIRED_ARGUMENT ],
|
385
|
+
[ '--health-check-timeout', '-T', GetoptLong::REQUIRED_ARGUMENT ],
|
386
|
+
[ '--timeout', '-t', GetoptLong::REQUIRED_ARGUMENT ],
|
387
|
+
[ '--unicorn-config=file', '-c', GetoptLong::REQUIRED_ARGUMENT ],
|
388
|
+
[ '--rackup-config=file', '-r', GetoptLong::REQUIRED_ARGUMENT ],
|
389
|
+
[ '--pid-file=file', '-p', GetoptLong::REQUIRED_ARGUMENT ],
|
390
|
+
[ '--rainbows', '-R', GetoptLong::NO_ARGUMENT ],
|
391
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ]
|
392
|
+
)
|
393
|
+
|
394
|
+
# Default settings
|
395
|
+
options = {
|
396
|
+
:app_dir => nil,
|
397
|
+
:check_url => nil,
|
398
|
+
:check_content => nil,
|
399
|
+
:env => 'development',
|
400
|
+
:timeout => 30,
|
401
|
+
:check_timeout => 5,
|
402
|
+
:unicorn_bin => 'unicorn',
|
403
|
+
:pid_file => nil,
|
404
|
+
:rackup_config => nil,
|
405
|
+
:unicorn_config => nil
|
406
|
+
}
|
407
|
+
|
408
|
+
# Process options
|
409
|
+
opts.each do |opt, arg|
|
410
|
+
case opt
|
411
|
+
when "--app-dir"
|
412
|
+
options[:app_dir] = arg.strip
|
413
|
+
|
414
|
+
when "--environment"
|
415
|
+
options[:env] = arg.strip
|
416
|
+
|
417
|
+
when "--timeout"
|
418
|
+
options[:timeout] = arg.to_i
|
419
|
+
|
420
|
+
when "--health-check-url"
|
421
|
+
options[:check_url] = arg.strip
|
422
|
+
|
423
|
+
when "--health-check-content"
|
424
|
+
options[:check_content] = arg.strip
|
425
|
+
|
426
|
+
when "--health-check-timeout"
|
427
|
+
options[:check_timeout] = arg.to_i
|
428
|
+
|
429
|
+
when "--unicorn-config"
|
430
|
+
options[:unicorn_config] = arg.strip
|
431
|
+
|
432
|
+
when "--rackup-config"
|
433
|
+
options[:rackup_config] = arg.strip
|
434
|
+
|
435
|
+
when "--pid-file"
|
436
|
+
options[:pid_file] = arg.strip
|
437
|
+
|
438
|
+
when "--rainbows"
|
439
|
+
options[:unicorn_bin] = 'rainbows'
|
440
|
+
|
441
|
+
when "--help"
|
442
|
+
show_help
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# Get command
|
447
|
+
command = ARGV.first
|
448
|
+
|
449
|
+
# Make sure we have the command
|
450
|
+
show_help("Please specify one of valid commands: #{VALID_COMMANDS.join(', ')}") unless command
|
451
|
+
|
452
|
+
# Check app directory
|
453
|
+
show_help("Please specify application directory!") unless options[:app_dir]
|
454
|
+
show_help("Please specify a valid application directory!") unless File.directory?(options[:app_dir])
|
455
|
+
options[:app_dir] = File.realpath(options[:app_dir])
|
456
|
+
|
457
|
+
#---------------------------------------------------------------------------------------------------
|
458
|
+
# Run commands
|
459
|
+
case command
|
460
|
+
when 'start'
|
461
|
+
start_application!(options)
|
462
|
+
|
463
|
+
when 'stop'
|
464
|
+
stop_application!(options, true)
|
465
|
+
|
466
|
+
when 'force-stop'
|
467
|
+
stop_application!(options, false)
|
468
|
+
|
469
|
+
when 'restart'
|
470
|
+
restart_application!(options, true)
|
471
|
+
|
472
|
+
when 'force-restart'
|
473
|
+
restart_application!(options, false)
|
474
|
+
|
475
|
+
when 'upgrade'
|
476
|
+
upgrade_application!(options)
|
477
|
+
|
478
|
+
else
|
479
|
+
show_help("ERROR: Invalid command: #{command}")
|
480
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unicorn-ctl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Oleksiy Kovyrin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: A script to control unicorn instances.
|
47
|
+
email:
|
48
|
+
- alexey@kovyrin.net
|
49
|
+
executables:
|
50
|
+
- unicornctl
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- bin/unicornctl
|
55
|
+
- LICENSE.txt
|
56
|
+
- CHANGELOG.md
|
57
|
+
- README.md
|
58
|
+
homepage: https://github.com/swiftype/unicorn-ctl
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.8.23
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Start/stop/restart/upgrade unicorn instances reliably.
|
83
|
+
test_files: []
|